diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,74 @@
+## Version 0.19.0.0 (03 June 2025)
+
+- Add `briefHangPoint` modifier. This allows one to specify the command length
+  after which the rendering will change from aligned with the end of the
+  command name to being indented on the next line.
+
+- Add `parserOptionGroup` for grouping Options together, similar to command
+  groups. Requires the breaking change of adding the `propGroup :: OptGroup`
+  field to `OptProperties`.
+
+
+## Version 0.18.1.0 (29 May 2023)
+
+- Change pretty printer layout algorithm used.
+
+  The layoutSmart algorithm appears to be extremely slow with some command line
+  sets, to the point where the program appears to hang.
+
+  Fixes issues:
+    * \# 476 - Stack executable 'hangs' with 0.17.1 and 0.18.0.
+
+- Render help text with `AnsiStyle` aware rendering functions.
+
+## Version 0.18.0.0 (22 May 2023)
+
+- Move to 'prettyprinter` library for pretty printing.
+
+  This is a potentially breaking change when one uses the '*Doc' family of functions
+  (like `headerDoc`) from `Options.Applicative`. However, as versions of
+  'ansi-wl-pprint > 1.0' export a compatible `Doc` type, this can be mitigated by
+  using a recent version.
+
+  One can also either import directly from `Options.Applicative.Help` or from the
+  `Prettyprinter` module of 'prettyprinter'.
+
+- Allow commands to be disambiguated in a similar manner to flags when the
+  `disambiguate` modifier is used.
+
+  This is a potentially breaking change as the internal `CmdReader` constructor
+  has been adapted so it is able to be inspected to a greater degree to support
+  finding prefix matches.
+
+## Version 0.17.1.0 (22 May 2023)
+
+- Widen bounds for `ansi-wl-pprint`. This supports the use of `prettyprinter`
+  in a non-breaking way, as the `ansi-wl-pprint > 1.0` support the newer
+  library.
+
+- Export `helpIndent` from `Options.Applicative`.
+
+- Export completion script generators from `Options.Applicative.BashCompletion`.
+
+- Add `simpleVersioner` utility for adding a '--version' option to a parser.
+
+- Improve documentation.
+
+- Drop support for GHC 7.0 and 7.2.
+
+## Version 0.17.0.0 (1 Feb 2022)
+
+- Make tabulation width configurable in usage texts.
+
+- Separate program name and description in ParserHelp type.
+
+- Add `helperWith` function, which can be easily used to
+  localize the help flag.
+
+- Improve usage texts when command names are long.
+
+- Improve Documentation.
+
 ## Version 0.16.1.0 (21 Nov 2020)
 
 - Guard `process` dependency behind an on by default flag.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
 # optparse-applicative
 
 [![Continuous Integration status][status-png]][status]
-[![Hackage matrix][hackage-matrix-png]][hackage-matrix]
 [![Hackage page (downloads and API reference)][hackage-png]][hackage]
 [![Hackage-Deps][hackage-deps-png]][hackage-deps]
 
@@ -44,6 +43,7 @@
 - [Applicative Do](#applicative-do)
 - [FAQ](#faq)
 - [How it works](#how-it-works)
+- [Tutorials](#tutorials)
 
 ## Introduction
 
@@ -75,7 +75,6 @@
 
 ```haskell
 import Options.Applicative
-import Data.Semigroup ((<>))
 
 data Sample = Sample
   { hello      :: String
@@ -303,8 +302,15 @@
 parsers are also able to be composed with standard combinators. For
 example: `optional :: Alternative f => f a -> f (Maybe a)` will
 mean the user is not required to provide input for the affected
-`Parser`.
+`Parser`. For example, the following parser will return `Nothing`
+instead of failing if the user doesn't supply an `output` option:
 
+```haskell
+optional $ strOption
+  ( long "output"
+ <> metavar "DIRECTORY" )
+```
+
 ### Running parsers
 
 Before we can run a `Parser`, we need to wrap it into a `ParserInfo`
@@ -504,7 +510,7 @@
 `many` or `some` combinator:
 
 ```haskell
-some (argument str (metavar "FILES..."))
+some (argument str (metavar "FILES"))
 ```
 
 Note that arguments starting with `-` are considered options by
@@ -527,22 +533,6 @@
 global options that apply to all of them. Typical examples are
 version control systems like `git`, or build tools like `cabal`.
 
-A command can be created using the `subparser` builder (or `hsubparser`,
-which is identical but for an additional `--help` option on each
-command), and commands can be added with the `command` modifier.
-For example
-
-```haskell
-subparser
-  ( command "add" (info addOptions ( progDesc "Add a file to the repository" ))
- <> command "commit" (info commitOptions ( progDesc "Record changes to the repository" ))
-  )
-```
-
-Each command takes a full `ParserInfo` structure, which will be
-used to extract a description for this command when generating a
-help text.
-
 Note that all the parsers appearing in a command need to have the
 same type.  For this reason, it is often best to use a sum type
 which has the same structure as the command itself. For example,
@@ -559,6 +549,22 @@
   ...
 ```
 
+A command can then be created using the `subparser` builder (or
+`hsubparser`, which is identical but for an additional `--help` option
+on each command), and commands can be added with the `command`
+modifier. For example,
+
+```haskell
+hsubparser
+  ( command "add" (info addCommand ( progDesc "Add a file to the repository" ))
+ <> command "commit" (info commitCommand ( progDesc "Record changes to the repository" ))
+  )
+```
+
+Each command takes a full `ParserInfo` structure, which will be
+used to extract a description for this command when generating a
+help text.
+
 Alternatively, you can directly return an `IO` action from a parser,
 and execute it using `join` from `Control.Monad`.
 
@@ -567,7 +573,7 @@
 stop :: IO ()
 
 opts :: Parser (IO ())
-opts = subparser
+opts = hsubparser
   ( command "start" (info (start <$> argument str idm) idm)
  <> command "stop"  (info (pure stop) idm) )
 
@@ -702,6 +708,11 @@
 
 ```
 
+**Note**. If an option name is a prefix of another option, then it
+will never be matched when disambiguation is on. See
+[#419](https://github.com/pcapriotti/optparse-applicative/issues/419)
+for more details.
+
 ### Customising the help screen
 
 optparse-applicative has a number of combinators to help customise
@@ -711,8 +722,8 @@
 specify a brief description or tagline for the program, and detailed
 information surrounding the generated option and command descriptions.
 
-Internally we actually use the [ansi-wl-pprint][ansi-wl-pprint]
-library, and one can use the `headerDoc` combinator and friends if
+Internally we actually use the [prettyprinter][prettyprinter]
+library, and one can supply either text or prettyprinter `Doc` elements if
 additional customisation is required.
 
 To display the usage text, the user may type `--help` if the `helper`
@@ -738,6 +749,52 @@
     p = prefs showHelpOnEmpty
 ```
 
+#### Option groups
+
+The `parserOptionGroup` function can be used to group options together under
+a common heading. For example, if we have:
+
+```haskell
+Args
+  <$> parseMain
+  <*> parserOptionGroup "Group A" parseA
+  <*> parserOptionGroup "Group B" parseB
+  <*> parseOther
+```
+
+Then the `--help` page `Available options` will look like:
+
+```
+Available options:
+  <main options>
+  <other options>
+
+Group A
+  <A options>
+
+Group B
+  <B options>
+```
+
+Caveats:
+
+- Parser groups are like command groups in that groups are listed in creation
+  order, and duplicate groups are consolidated.
+
+- Nested groups are indented:
+
+    ```haskell
+    parserOptionGroup "Group Outer" (parserOptionGroup "Group Inner" parseA)
+    ```
+
+    Will render as:
+
+    ```
+    Group Outer
+    - Group Inner
+      ...
+    ```
+
 ### Command groups
 
 One experimental feature which may be useful for programs with many
@@ -753,11 +810,11 @@
 hello = Hello <$> many (argument str (metavar "TARGET..."))
 
 sample :: Parser Sample
-sample = subparser
+sample = hsubparser
        ( command "hello" (info hello (progDesc "Print greeting"))
       <> command "goodbye" (info (pure Goodbye) (progDesc "Say goodbye"))
        )
-      <|> subparser
+      <|> hsubparser
        ( command "bonjour" (info hello (progDesc "Print greeting"))
       <> command "au-revoir" (info (pure Goodbye) (progDesc "Say goodbye"))
       <> commandGroup "French commands:"
@@ -1005,6 +1062,14 @@
 See [this blog post][blog] for a more detailed explanation based on a
 simplified implementation.
 
+## Tutorials
+
+These are some tutorials found on the web:
+
+- [A Gentle Introduction to optparse-applicative](https://prborges.com/2023/introduction-to-optparse-applicative/).
+- [optparse-applicative quick start](https://ro-che.info/articles/2016-12-30-optparse-applicative-quick-start).
+- [Applicative Options Parsing in Haskell](https://thoughtbot.com/blog/applicative-options-parsing-in-haskell).
+
  [aeson]: http://hackage.haskell.org/package/aeson
  [applicative]: http://hackage.haskell.org/package/base/docs/Control-Applicative.html
  [arrows]: http://www.haskell.org/arrows/syntax.html
@@ -1013,13 +1078,11 @@
  [blog]: http://paolocapriotti.com/blog/2012/04/27/applicative-option-parser/
  [hackage]: http://hackage.haskell.org/package/optparse-applicative
  [hackage-png]: http://img.shields.io/hackage/v/optparse-applicative.svg
- [hackage-matrix]: https://matrix.hackage.haskell.org/package/optparse-applicative
- [hackage-matrix-png]: https://matrix.hackage.haskell.org/api/v2/packages/optparse-applicative/badge
  [hackage-deps]: http://packdeps.haskellers.com/reverse/optparse-applicative
  [hackage-deps-png]: https://img.shields.io/hackage-deps/v/optparse-applicative.svg
  [monoid]: http://hackage.haskell.org/package/base/docs/Data-Monoid.html
  [semigroup]: http://hackage.haskell.org/package/base/docs/Data-Semigroup.html
  [parsec]: http://hackage.haskell.org/package/parsec
- [status]: http://travis-ci.org/pcapriotti/optparse-applicative?branch=master
- [status-png]: https://api.travis-ci.org/pcapriotti/optparse-applicative.svg?branch=master
- [ansi-wl-pprint]: http://hackage.haskell.org/package/ansi-wl-pprint
+ [status]: https://github.com/pcapriotti/optparse-applicative/actions/workflows/haskell-ci.yml
+ [status-png]: https://github.com/pcapriotti/optparse-applicative/workflows/Haskell-CI/badge.svg
+ [prettyprinter]: http://hackage.haskell.org/package/prettyprinter
diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal
--- a/optparse-applicative.cabal
+++ b/optparse-applicative.cabal
@@ -1,5 +1,5 @@
 name:                optparse-applicative
-version:             0.16.1.0
+version:             0.19.0.0
 synopsis:            Utilities and combinators for parsing command line options
 description:
     optparse-applicative is a haskell library for parsing options
@@ -36,25 +36,40 @@
                      tests/helponemptysub.err.txt
                      tests/long_equals.err.txt
                      tests/formatting.err.txt
+                     tests/formatting-long-subcommand.err.txt
                      tests/nested.err.txt
                      tests/optional.err.txt
+                     tests/parser_group_all_grouped.err.txt
+                     tests/parser_group_basic.err.txt
+                     tests/parser_group_command_groups.err.txt
+                     tests/parser_group_duplicate_command_groups.err.txt
+                     tests/parser_group_duplicates.err.txt
+                     tests/parser_group_nested.err.txt
                      tests/nested_optional.err.txt
                      tests/subparsers.err.txt
 
 homepage:            https://github.com/pcapriotti/optparse-applicative
 bug-reports:         https://github.com/pcapriotti/optparse-applicative/issues
 tested-with:
-  GHC==7.0.4,
-  GHC==7.2.2,
-  GHC==7.4.2,
-  GHC==7.6.3,
-  GHC==7.8.4,
-  GHC==7.10.3,
-  GHC==8.0.2,
-  GHC==8.2.2,
-  GHC==8.4.4,
-  GHC==8.6.5,
-  GHC==8.8.1
+  GHC==9.12.1
+  GHC==9.10.1
+  GHC==9.8.4
+  GHC==9.6.1
+  GHC==9.4.4
+  GHC==9.2.7
+  GHC==9.0.2
+  GHC==8.10.7
+  GHC==8.8.4
+  GHC==8.6.5
+  GHC==8.4.4
+  GHC==8.2.2
+  GHC==8.0.2
+  GHC==7.10.3
+  GHC==7.8.4
+  GHC==7.6.3
+  GHC==7.4.2
+  GHC==7.2.2
+  GHC==7.0.4
 
 source-repository head
   type:     git
@@ -94,16 +109,17 @@
                      , Options.Applicative.Types
                      , Options.Applicative.Internal
 
-  build-depends:       base                            == 4.*
-                     , transformers                    >= 0.2 && < 0.6
-                     , transformers-compat             >= 0.3 && < 0.7
-                     , ansi-wl-pprint                  >= 0.6.8 && < 0.7
+  build-depends:       base                            >= 4.5   && < 5
+                     , text                            >= 1.2
+                     , transformers                    >= 0.5   && < 0.7
+                     , prettyprinter                   >= 1.7   && < 1.8
+                     , prettyprinter-ansi-terminal     >= 1.1.2 && < 1.2
 
   if flag(process)
-    build-depends:     process                         >= 1.0 && < 1.7
+    build-depends:     process                         >= 1.0   && < 1.7
 
   if !impl(ghc >= 8)
-    build-depends:     semigroups                      >= 0.10 && < 0.20
+    build-depends:     semigroups                      >= 0.10  && < 0.21
                      , fail                            == 4.9.*
 
 test-suite tests
@@ -122,10 +138,17 @@
                      , Examples.Commands
                      , Examples.Formatting
                      , Examples.Hello
+                     , Examples.LongSub
+                     , Examples.ParserGroup.AllGrouped
+                     , Examples.ParserGroup.Basic
+                     , Examples.ParserGroup.CommandGroups
+                     , Examples.ParserGroup.DuplicateCommandGroups
+                     , Examples.ParserGroup.Duplicates
+                     , Examples.ParserGroup.Nested
 
   build-depends:       base
                      , optparse-applicative
-                     , QuickCheck                      >= 2.8 && < 2.15
+                     , QuickCheck                      >= 2.8 && < 2.16
 
   if !impl(ghc >= 8)
     build-depends:     semigroups
diff --git a/src/Options/Applicative.hs b/src/Options/Applicative.hs
--- a/src/Options/Applicative.hs
+++ b/src/Options/Applicative.hs
@@ -7,7 +7,7 @@
   -- See <https://github.com/pcapriotti/optparse-applicative> for a tutorial,
   -- and a general introduction to applicative option parsers.
   --
-  -- See the sections below for more detail
+  -- See the sections below for more detail.
 
   -- * Exported modules
   --
@@ -18,7 +18,7 @@
   --
   -- | A 'Parser' is the core type in optparse-applicative. A value of type
   -- @Parser a@ represents a specification for a set of options, which will
-  -- yield a value of type a when the command line arguments are successfully
+  -- yield a value of type @a@ when the command line arguments are successfully
   -- parsed.
   --
   -- There are several types of primitive 'Parser'.
@@ -74,13 +74,14 @@
   abortOption,
   infoOption,
   helper,
+  simpleVersioner,
 
   -- ** Modifiers
   --
   -- | 'Parser' builders take a modifier, which represents a modification of the
   -- properties of an option, and can be composed as a monoid.
   --
-  -- Contraints are often used to ensure that the modifiers can be sensibly applied.
+  -- Constraints are often used to ensure that the modifiers can be sensibly applied.
   -- For example, positional arguments can't be specified by long or short names,
   -- so the 'HasName' constraint is used to ensure we have a flag or option.
   Mod,
@@ -104,6 +105,7 @@
   completer,
   idm,
   mappend,
+  parserOptionGroup,
 
   OptionFields,
   FlagFields,
@@ -114,18 +116,20 @@
   HasCompleter,
   HasValue,
   HasMetavar,
+
   -- ** Readers
   --
   -- | A reader is used by the 'option' and 'argument' builders to parse
   -- the data passed by the user on the command line into a data type.
   --
   -- The most common are 'str' which is used for 'String' like types,
-  -- including 'ByteString' and 'Text'; and 'auto', which uses the 'Read'
-  -- typeclass, and is good for simple types like 'Int' or 'Double'.
+  -- including t'Data.ByteString.ByteString' and t'Data.Text.Text'; and 'auto',
+  -- which uses the 'Read' typeclass, and is good for simple types like 'Int' or
+  -- 'Double'.
   --
   -- More complex types can use the 'eitherReader' or 'maybeReader'
   -- functions to pattern match or use a more expressive parser like a
-  -- member of the 'Parsec' family.
+  -- member of the @Parsec@ family.
   ReadM,
 
   auto,
@@ -197,6 +201,8 @@
   columns,
   helpLongEquals,
   helpShowGlobals,
+  helpIndent,
+  briefHangPoint,
   defaultPrefs,
 
   -- * Completions
diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs
--- a/src/Options/Applicative/BashCompletion.hs
+++ b/src/Options/Applicative/BashCompletion.hs
@@ -4,7 +4,11 @@
 -- <http://github.com/pcapriotti/optparse-applicative/wiki/Bash-Completion the wiki>
 -- for more information on bash completion.
 module Options.Applicative.BashCompletion
-  ( bashCompletionParser
+  ( bashCompletionParser,
+
+    bashCompletionScript,
+    fishCompletionScript,
+    zshCompletionScript,
   ) where
 
 import Control.Applicative
@@ -34,11 +38,15 @@
 bashCompletionParser :: ParserInfo a -> ParserPrefs -> Parser CompletionResult
 bashCompletionParser pinfo pprefs = complParser
   where
-    failure opts = CompletionResult
-      { execCompletion = \progn -> unlines <$> opts progn }
+    returnCompletions opts =
+      CompletionResult $
+        \progn -> unlines <$> opts progn
 
+    scriptRequest =
+      CompletionResult . fmap pure
+
     complParser = asum
-      [ failure <$>
+      [ returnCompletions <$>
         (  bashCompletionQuery pinfo pprefs
         -- To get rich completions, one just needs the first
         -- command. To customise the lengths, use either of
@@ -53,15 +61,13 @@
         <*> (many . strOption) (long "bash-completion-word"
                                   `mappend` internal)
         <*> option auto (long "bash-completion-index" `mappend` internal) )
-      , failure <$>
-          (bashCompletionScript <$>
-            strOption (long "bash-completion-script" `mappend` internal))
-      , failure <$>
-          (fishCompletionScript <$>
-            strOption (long "fish-completion-script" `mappend` internal))
-      , failure <$>
-          (zshCompletionScript <$>
-            strOption (long "zsh-completion-script" `mappend` internal))
+
+      , scriptRequest . bashCompletionScript <$>
+            strOption (long "bash-completion-script" `mappend` internal)
+      , scriptRequest . fishCompletionScript <$>
+            strOption (long "fish-completion-script" `mappend` internal)
+      , scriptRequest . zshCompletionScript <$>
+            strOption (long "zsh-completion-script" `mappend` internal)
       ]
 
 bashCompletionQuery :: ParserInfo a -> ParserPrefs -> Richness -> [String] -> Int -> String -> IO [String]
@@ -107,11 +113,11 @@
         -> return []
          | otherwise
         -> run_completer (crCompleter rdr)
-      CmdReader _ ns p
+      CmdReader _ ns
          | argumentIsUnreachable reachability
         -> return []
          | otherwise
-        -> return . add_cmd_help p $ filter_names ns
+        -> return . with_cmd_help $ filter (is_completion . fst) ns
 
     -- When doing enriched completions, add any help specified
     -- to the completion variables (tab separated).
@@ -126,30 +132,28 @@
 
     -- When doing enriched completions, add the command description
     -- to the completion variables (tab separated).
-    add_cmd_help :: Functor f => (String -> Maybe (ParserInfo a)) -> f String -> f String
-    add_cmd_help p = case richness of
-      Standard ->
-        id
-      Enriched _ len ->
-        fmap $ \cmd ->
-          let h = p cmd >>= unChunk . infoProgDesc
-          in  maybe cmd (\h' -> cmd ++ "\t" ++ render_line len h') h
+    with_cmd_help :: Functor f => f (String, ParserInfo a) -> f String
+    with_cmd_help =
+      case richness of
+        Standard ->
+          fmap fst
+        Enriched _ len ->
+          fmap $ \(cmd, cmdInfo) ->
+            let h = unChunk (infoProgDesc cmdInfo)
+            in  maybe cmd (\h' -> cmd ++ "\t" ++ render_line len h') h
 
     show_names :: [OptName] -> [String]
-    show_names = filter_names . map showOption
+    show_names = filter is_completion . map showOption
 
     -- We only want to show a single line in the completion results description.
     -- If there was a line break, it would come across as a different completion
     -- possibility.
     render_line :: Int -> Doc -> String
-    render_line len doc = case lines (displayS (renderPretty 1 len doc) "") of
+    render_line len doc = case lines (prettyString 1 len doc) of
       [] -> ""
       [x] -> x
       x : _ -> x ++ "..."
 
-    filter_names :: [String] -> [String]
-    filter_names = filter is_completion
-
     run_completer :: Completer -> IO [String]
     run_completer c = runCompleter c (fromMaybe "" (listToMaybe ws''))
 
@@ -161,8 +165,9 @@
         w:_ -> isPrefixOf w
         _ -> const True
 
-bashCompletionScript :: String -> String -> IO [String]
-bashCompletionScript prog progn = return
+-- | Generated bash shell completion script
+bashCompletionScript :: String -> String -> String
+bashCompletionScript prog progn = unlines
   [ "_" ++ progn ++ "()"
   , "{"
   , "    local CMDLINE"
@@ -196,8 +201,10 @@
 
 Tab characters separate items from descriptions.
 -}
-fishCompletionScript :: String -> String -> IO [String]
-fishCompletionScript prog progn = return
+
+-- | Generated fish shell completion script 
+fishCompletionScript :: String -> String -> String
+fishCompletionScript prog progn = unlines
   [ " function _" ++ progn
   , "    set -l cl (commandline --tokenize --current-process)"
   , "    # Hack around fish issue #3934"
@@ -219,8 +226,9 @@
   , "complete --no-files --command " ++ progn ++ " --arguments '(_"  ++ progn ++  ")'"
   ]
 
-zshCompletionScript :: String -> String -> IO [String]
-zshCompletionScript prog progn = return
+-- | Generated zsh shell completion script
+zshCompletionScript :: String -> String -> String
+zshCompletionScript prog progn = unlines
   [ "#compdef " ++ progn
   , ""
   , "local request"
diff --git a/src/Options/Applicative/Builder.hs b/src/Options/Applicative/Builder.hs
--- a/src/Options/Applicative/Builder.hs
+++ b/src/Options/Applicative/Builder.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE CPP #-}
-
 module Options.Applicative.Builder (
   -- * Parser builders
   --
@@ -50,6 +49,7 @@
   completer,
   idm,
   mappend,
+  parserOptionGroup,
 
   -- * Readers
   --
@@ -89,6 +89,8 @@
   columns,
   helpLongEquals,
   helpShowGlobals,
+  helpIndent,
+  briefHangPoint,
   prefs,
   defaultPrefs,
 
@@ -107,8 +109,8 @@
   ) where
 
 import Control.Applicative
-#if __GLASGOW_HASKELL__ <= 802
-import Data.Semigroup hiding (option)
+#if __GLASGOW_HASKELL__ < 804
+import Data.Semigroup hiding (Option, option)
 #endif
 import Data.String (fromString, IsString)
 
@@ -118,6 +120,7 @@
 import Options.Applicative.Types
 import Options.Applicative.Help.Pretty
 import Options.Applicative.Help.Chunk
+import Options.Applicative.Internal (mapParserOptions)
 
 -- Readers --
 
@@ -189,7 +192,7 @@
 help :: String -> Mod f a
 help s = optionMod $ \p -> p { propHelp = paragraph s }
 
--- | Specify the help text for an option as a 'Text.PrettyPrint.ANSI.Leijen.Doc'
+-- | Specify the help text for an option as a 'Prettyprinter.Doc AnsiStyle'
 -- value.
 helpDoc :: Maybe Doc -> Mod f a
 helpDoc doc = optionMod $ \p -> p { propHelp = Chunk doc }
@@ -215,11 +218,11 @@
 -- | Apply a function to the option description in the usage text.
 --
 -- > import Options.Applicative.Help
--- > flag' () (short 't' <> style bold)
+-- > flag' () (short 't' <> style (annotate bold))
 --
 -- /NOTE/: This builder is more flexible than its name and example
 -- allude. One of the motivating examples for its addition was to
--- used `const` to completely replace the usage text of an option.
+-- use `const` to completely replace the usage text of an option.
 style :: ( Doc -> Doc ) -> Mod f a
 style x = optionMod $ \p ->
   p { propDescMod = Just x }
@@ -273,12 +276,17 @@
 
 -- | Builder for a command parser. The 'command' modifier can be used to
 -- specify individual commands.
+--
+-- By default, sub-parsers allow backtracking to their parent's options when
+-- they are completed. To allow full mixing of parent and sub-parser options,
+-- turn on 'subparserInline'; otherwise, to disable backtracking completely,
+-- use 'noBacktrack'.
 subparser :: Mod CommandFields a -> Parser a
 subparser m = mkParser d g rdr
   where
     Mod _ d g = metavar "COMMAND" `mappend` m
-    (groupName, cmds, subs) = mkCommand m
-    rdr = CmdReader groupName cmds subs
+    (groupName, cmds) = mkCommand m
+    rdr = CmdReader groupName cmds
 
 -- | Builder for an argument parser.
 argument :: ReadM a -> Mod ArgumentFields a -> Parser a
@@ -374,6 +382,56 @@
     crdr = CReader (optCompleter fields) r
     rdr = OptReader (optNames fields) crdr (optNoArgError fields)
 
+-- | Prepends a group to 'OptProperties'. Nested groups are indented e.g.
+--
+-- @
+--   optPropertiesGroup "Group Outer" (optPropertiesGroup "Group Inner" o)
+-- @
+--
+-- will render as:
+--
+-- @
+--  Group Outer
+--  - Group Inner
+--    ...
+-- @
+optPropertiesGroup :: String -> OptProperties -> OptProperties
+optPropertiesGroup g o = o { propGroup = OptGroup (g : oldGroup) }
+  where
+    OptGroup oldGroup = propGroup o
+
+-- | Prepends a group per 'optPropertiesGroup'.
+optionGroup :: String -> Option a -> Option a
+optionGroup grp o = o { optProps = props' }
+  where
+    props' = optPropertiesGroup grp (optProps o)
+
+-- | Group options together under a common heading in the help text.
+--
+-- For example, if we have:
+--
+-- > Args
+-- >   <$> parseMain
+-- >   <*> parserOptionGroup "Group A" parseA
+-- >   <*> parserOptionGroup "Group B" parseB
+-- >   <*> parseOther
+--
+-- Then the help page will look like:
+--
+-- > Available options:
+-- >   <main options>
+-- >   <other options>
+-- >
+-- > Group A
+-- >   <A options>
+-- >
+-- > Group B
+-- >   <B options>
+--
+-- @since 0.19.0.0
+parserOptionGroup :: String -> Parser a -> Parser a
+parserOptionGroup g = mapParserOptions (optionGroup g)
+
 -- | Modifier for 'ParserInfo'.
 newtype InfoMod a = InfoMod
   { applyInfoMod :: ParserInfo a -> ParserInfo a }
@@ -385,7 +443,7 @@
 instance Semigroup (InfoMod a) where
   m1 <> m2 = InfoMod $ applyInfoMod m2 . applyInfoMod m1
 
--- | Show a full description in the help text of this parser.
+-- | Show a full description in the help text of this parser (default).
 fullDesc :: InfoMod a
 fullDesc = InfoMod $ \i -> i { infoFullDesc = True }
 
@@ -397,7 +455,7 @@
 header :: String -> InfoMod a
 header s = InfoMod $ \i -> i { infoHeader = paragraph s }
 
--- | Specify a header for this parser as a 'Text.PrettyPrint.ANSI.Leijen.Doc'
+-- | Specify a header for this parser as a 'Prettyprinter.Doc AnsiStyle'
 -- value.
 headerDoc :: Maybe Doc -> InfoMod a
 headerDoc doc = InfoMod $ \i -> i { infoHeader = Chunk doc }
@@ -406,7 +464,7 @@
 footer :: String -> InfoMod a
 footer s = InfoMod $ \i -> i { infoFooter = paragraph s }
 
--- | Specify a footer for this parser as a 'Text.PrettyPrint.ANSI.Leijen.Doc'
+-- | Specify a footer for this parser as a 'Prettyprinter.Doc AnsiStyle'
 -- value.
 footerDoc :: Maybe Doc -> InfoMod a
 footerDoc doc = InfoMod $ \i -> i { infoFooter = Chunk doc }
@@ -415,7 +473,7 @@
 progDesc :: String -> InfoMod a
 progDesc s = InfoMod $ \i -> i { infoProgDesc = paragraph s }
 
--- | Specify a short program description as a 'Text.PrettyPrint.ANSI.Leijen.Doc'
+-- | Specify a short program description as a 'Prettyprinter.Doc AnsiStyle'
 -- value.
 progDescDoc :: Maybe Doc -> InfoMod a
 progDescDoc doc = InfoMod $ \i -> i { infoProgDesc = Chunk doc }
@@ -517,11 +575,19 @@
 helpLongEquals :: PrefsMod
 helpLongEquals = PrefsMod $ \p -> p { prefHelpLongEquals = True }
 
--- | Show global help information in subparser usage
+-- | Show global help information in subparser usage.
 helpShowGlobals :: PrefsMod
-helpShowGlobals = PrefsMod $ \p -> p { prefHelpShowGlobal = True}
+helpShowGlobals = PrefsMod $ \p -> p { prefHelpShowGlobal = True }
 
+-- | Set fill width in help text presentation.
+helpIndent :: Int -> PrefsMod
+helpIndent w = PrefsMod $ \p -> p { prefTabulateFill = w }
 
+-- | Set the width at which to hang the brief help text.
+briefHangPoint :: Int -> PrefsMod
+briefHangPoint php = PrefsMod $ \p -> p { prefBriefHangPoint = php }
+
+
 -- | Create a `ParserPrefs` given a modifier
 prefs :: PrefsMod -> ParserPrefs
 prefs m = applyPrefsMod m base
@@ -534,7 +600,9 @@
       , prefBacktrack = Backtrack
       , prefColumns = 80
       , prefHelpLongEquals = False
-      , prefHelpShowGlobal = False }
+      , prefHelpShowGlobal = False
+      , prefTabulateFill = 24
+      , prefBriefHangPoint = 35 }
 
 -- Convenience shortcuts
 
diff --git a/src/Options/Applicative/Builder/Completer.hs b/src/Options/Applicative/Builder/Completer.hs
--- a/src/Options/Applicative/Builder/Completer.hs
+++ b/src/Options/Applicative/Builder/Completer.hs
@@ -6,6 +6,8 @@
   , listIOCompleter
   , listCompleter
   , bashCompleter
+
+  , requote
   ) where
 
 import Control.Applicative
diff --git a/src/Options/Applicative/Builder/Internal.hs b/src/Options/Applicative/Builder/Internal.hs
--- a/src/Options/Applicative/Builder/Internal.hs
+++ b/src/Options/Applicative/Builder/Internal.hs
@@ -120,7 +120,8 @@
 -- Modifiers are instances of 'Monoid', and can be composed as such.
 --
 -- One rarely needs to deal with modifiers directly, as most of the times it is
--- sufficient to pass them to builders (such as 'strOption' or 'flag') to
+-- sufficient to pass them to builders (such as 'Options.Applicative.strOption'
+-- or 'Options.Applicative.flag') to
 -- create options (see 'Options.Applicative.Builder').
 data Mod f a = Mod (f a -> f a)
                    (DefaultProp a)
@@ -150,10 +151,11 @@
   , propShowDefault = Nothing
   , propDescMod = Nothing
   , propShowGlobal = True
+  , propGroup = OptGroup []
   }
 
-mkCommand :: Mod CommandFields a -> (Maybe String, [String], String -> Maybe (ParserInfo a))
-mkCommand m = (group, map fst cmds, (`lookup` cmds))
+mkCommand :: Mod CommandFields a -> (Maybe String, [(String, ParserInfo a)])
+mkCommand m = (group, cmds)
   where
     Mod f _ _ = m
     CommandFields cmds group = f (CommandFields [] Nothing)
@@ -184,7 +186,8 @@
 
 -- | Hide this option completely from the help text
 --
--- Use 'hidden' if the option should remain visible in the full description.
+-- Use 'Options.Applicative.hidden' if the option should remain visible in the
+-- full description.
 internal :: Mod f a
 internal = optionMod $ \p -> p { propVisibility = Internal }
 
diff --git a/src/Options/Applicative/Common.hs b/src/Options/Applicative/Common.hs
--- a/src/Options/Applicative/Common.hs
+++ b/src/Options/Applicative/Common.hs
@@ -166,24 +166,31 @@
   searchParser $ \opt -> do
     when (isArg (optMain opt)) cut
     case optMain opt of
-      CmdReader _ _ f ->
-        case (f arg, prefBacktrack prefs) of
-          (Just subp, NoBacktrack) -> lift $ do
+      CmdReader _ cs -> do
+        subp <- hoistList (cmdMatches cs)
+        case prefBacktrack prefs of
+          NoBacktrack -> lift $ do
             args <- get <* put []
             fmap pure . lift $ enterContext arg subp *> runParserInfo subp args <* exitContext
 
-          (Just subp, Backtrack) -> fmap pure . lift . StateT $ \args ->
+          Backtrack -> fmap pure . lift . StateT $ \args ->
             enterContext arg subp *> runParser (infoPolicy subp) CmdStart (infoParser subp) args <* exitContext
 
-          (Just subp, SubparserInline) -> lift $ do
+          SubparserInline -> lift $ do
             lift $ enterContext arg subp
             return $ infoParser subp
 
-          (Nothing, _)  -> mzero
       ArgReader rdr ->
         fmap pure . lift . lift $ runReadM (crReader rdr) arg
-      _ -> mzero
 
+      _ ->
+        mzero
+
+  where
+    cmdMatches cs
+      | prefDisambiguate prefs = snd <$> filter (isPrefixOf arg . fst) cs
+      | otherwise = maybeToList (lookup arg cs)
+
 stepParser :: MonadP m => ParserPrefs -> ArgPolicy -> String
            -> Parser a -> NondetT (StateT Args m) (Parser a)
 stepParser pprefs AllPositionals arg p =
@@ -261,8 +268,8 @@
 
 -- | Like 'mapParser', but collect the results in a tree structure.
 treeMapParser :: (forall x. ArgumentReachability -> Option x -> b)
-          -> Parser a
-          -> OptTree b
+              -> Parser a
+              -> OptTree b
 treeMapParser g = simplify . go False g
   where
     has_default :: Parser a -> Bool
diff --git a/src/Options/Applicative/Extra.hs b/src/Options/Applicative/Extra.hs
--- a/src/Options/Applicative/Extra.hs
+++ b/src/Options/Applicative/Extra.hs
@@ -4,7 +4,9 @@
   --
   -- | This module contains high-level functions to run parsers.
   helper,
+  helperWith,
   hsubparser,
+  simpleVersioner,
   execParser,
   customExecParser,
   execParserPure,
@@ -47,16 +49,32 @@
 
 helper :: Parser (a -> a)
 helper =
+  helperWith (mconcat [
+    long "help",
+    short 'h',
+    help "Show this help text"
+  ])
+
+-- | Like helper, but with a minimal set of modifiers that can be extended
+-- as desired.
+--
+-- > opts :: ParserInfo Sample
+-- > opts = info (sample <**> helperWith (mconcat [
+-- >          long "help",
+-- >          short 'h',
+-- >          help "Show this help text",
+-- >          hidden
+-- >        ])) mempty
+helperWith :: Mod OptionFields (a -> a) -> Parser (a -> a)
+helperWith modifiers =
   option helpReader $
     mconcat
-      [ long "help",
-        short 'h',
-        help "Show this help text",
-        value id,
+      [ value id,
         metavar "",
         noGlobal,
         noArgError (ShowHelpText Nothing),
-        hidden
+        hidden,
+        modifiers
       ]
   where
     helpReader = do
@@ -71,11 +89,24 @@
 hsubparser m = mkParser d g rdr
   where
     Mod _ d g = metavar "COMMAND" `mappend` m
-    (groupName, cmds, subs) = mkCommand m
-    rdr = CmdReader groupName cmds (fmap add_helper . subs)
+    (groupName, cmds) = mkCommand m
+    rdr = CmdReader groupName ((fmap . fmap) add_helper cmds)
     add_helper pinfo = pinfo
       { infoParser = infoParser pinfo <**> helper }
 
+-- | A hidden \"--version\" option that displays the version.
+--
+-- > opts :: ParserInfo Sample
+-- > opts = info (sample <**> simpleVersioner "v1.2.3") mempty
+simpleVersioner :: String -- ^ Version string to be shown
+                -> Parser (a -> a)
+simpleVersioner version = infoOption version $
+  mconcat
+    [ long "version"
+    , help "Show version information"
+    , hidden
+    ]
+
 -- | Run a program description.
 --
 -- Parse command line arguments. Display help text and exit if any parse error
@@ -136,7 +167,7 @@
 --
 -- This function can be used, for example, to show the help text for a parser:
 --
--- @handleParseResult . Failure $ parserFailure pprefs pinfo ShowHelpText mempty@
+-- @handleParseResult . Failure $ parserFailure pprefs pinfo (ShowHelpText Nothing) mempty@
 parserFailure :: ParserPrefs -> ParserInfo a
               -> ParseError -> [Context]
               -> ParserFailure ParserHelp
@@ -197,9 +228,10 @@
       InfoMsg _
         -> mempty
       _
-        -> usageHelp $ vcatChunks
-          [ pure . parserUsage pprefs (infoParser i) . unwords $ progn : names
-          , fmap (indent 2) . infoProgDesc $ i ]
+        -> mconcat [
+            usageHelp (pure . parserUsage pprefs (infoParser i) . unwords $ progn : names)
+          , descriptionHelp (infoProgDesc i)
+          ]
 
     error_help = errorHelp $ case msg of
       ShowHelpText {}
@@ -285,10 +317,10 @@
               OptReader ns _ _ -> fmap showOption ns
               FlagReader ns _  -> fmap showOption ns
               ArgReader _      -> []
-              CmdReader _ ns _  | argumentIsUnreachable reachability
+              CmdReader _ ns    | argumentIsUnreachable reachability
                                -> []
                                 | otherwise
-                               -> ns
+                               -> fst <$> ns
       _
         -> mempty
 
diff --git a/src/Options/Applicative/Help/Chunk.hs b/src/Options/Applicative/Help/Chunk.hs
--- a/src/Options/Applicative/Help/Chunk.hs
+++ b/src/Options/Applicative/Help/Chunk.hs
@@ -22,7 +22,7 @@
 
 import Options.Applicative.Help.Pretty
 
--- | The free monoid on a semigroup 'a'.
+-- | The free monoid on a semigroup @a@.
 newtype Chunk a = Chunk
   { unChunk :: Maybe a }
   deriving (Eq, Show)
@@ -53,7 +53,8 @@
   mzero = Chunk mzero
   mplus m1 m2 = Chunk $ mplus (unChunk m1) (unChunk m2)
 
--- | Given a semigroup structure on 'a', return a monoid structure on 'Chunk a'.
+-- | Given a semigroup structure on @a@, return a monoid structure on
+-- @'Chunk' a@.
 --
 -- Note that this is /not/ the same as 'liftA2'.
 chunked :: (a -> a -> a)
@@ -115,7 +116,7 @@
 -- > extractChunk . stringChunk = string
 stringChunk :: String -> Chunk Doc
 stringChunk "" = mempty
-stringChunk s = pure (string s)
+stringChunk s = pure (pretty s)
 
 -- | Convert a paragraph into a 'Chunk'.  The resulting chunk is composed by the
 -- words of the original paragraph separated by softlines, so it will be
@@ -128,12 +129,9 @@
 paragraph = foldr (chunked (</>) . stringChunk) mempty
           . words
 
-tabulate' :: Int -> [(Doc, Doc)] -> Chunk Doc
-tabulate' _ [] = mempty
-tabulate' size table = pure $ vcat
+-- | Display pairs of strings in a table.
+tabulate :: Int -> [(Doc, Doc)] -> Chunk Doc
+tabulate _ [] = mempty
+tabulate size table = pure $ vcat
   [ indent 2 (fillBreak size key <+> value)
   | (key, value) <- table ]
-
--- | Display pairs of strings in a table.
-tabulate :: [(Doc, Doc)] -> Chunk Doc
-tabulate = tabulate' 24
diff --git a/src/Options/Applicative/Help/Core.hs b/src/Options/Applicative/Help/Core.hs
--- a/src/Options/Applicative/Help/Core.hs
+++ b/src/Options/Applicative/Help/Core.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 module Options.Applicative.Help.Core (
   cmdDesc,
   briefDesc,
@@ -9,6 +10,7 @@
   headerHelp,
   suggestionsHelp,
   usageHelp,
+  descriptionHelp,
   bodyHelp,
   footerHelp,
   globalsHelp,
@@ -17,16 +19,18 @@
   parserGlobals
   ) where
 
-import Control.Applicative
-import Control.Monad (guard)
-import Data.Function (on)
-import Data.List (sort, intersperse, groupBy)
-import Data.Foldable (any, foldl')
-import Data.Maybe (maybeToList, catMaybes, fromMaybe)
-import Data.Monoid (mempty)
-import Data.Semigroup (Semigroup (..))
-import Prelude hiding (any)
+import           Control.Applicative
+import           Control.Monad (guard)
 
+import           Data.Foldable (any, foldl')
+import           Data.Function (on)
+import qualified Data.List as List
+import           Data.List.NonEmpty (NonEmpty ((:|)))
+import qualified Data.List.NonEmpty as NE
+import           Data.Maybe (fromMaybe, catMaybes)
+
+import           Prelude hiding (any)
+
 import Options.Applicative.Common
 import Options.Applicative.Types
 import Options.Applicative.Help.Pretty
@@ -44,16 +48,17 @@
 safelast = foldl' (const Just) Nothing
 
 -- | Generate description for a single option.
-optDesc :: ParserPrefs -> OptDescStyle -> ArgumentReachability -> Option a -> (Chunk Doc, Parenthetic)
+optDesc :: ParserPrefs -> OptDescStyle -> ArgumentReachability -> Option a -> (OptGroup, Chunk Doc, Parenthetic)
 optDesc pprefs style _reachability opt =
   let names =
-        sort . optionNames . optMain $ opt
+        List.sort . optionNames . optMain $ opt
       meta =
         stringChunk $ optMetaVar opt
+      grp = propGroup $ optProps opt
       descs =
-        map (string . showOption) names
+        map (pretty . showOption) names
       descriptions =
-        listToChunk (intersperse (descSep style) descs)
+        listToChunk (List.intersperse (descSep style) descs)
       desc
         | prefHelpLongEquals pprefs && not (isEmpty meta) && any isLongName (safelast names) =
           descriptions <> stringChunk "=" <> meta
@@ -80,20 +85,19 @@
           desc
       modified =
         maybe id fmap (optDescMod opt) rendered
-   in (modified, wrapping)
+   in (grp, modified, wrapping)
 
 -- | Generate descriptions for commands.
-cmdDesc :: Parser a -> [(Maybe String, Chunk Doc)]
-cmdDesc = mapParser desc
+cmdDesc :: ParserPrefs -> Parser a -> [(Maybe String, Chunk Doc)]
+cmdDesc pprefs = mapParser desc
   where
     desc _ opt =
       case optMain opt of
-        CmdReader gn cmds p ->
+        CmdReader gn cmds ->
           (,) gn $
-            tabulate
-              [ (string cmd, align (extractChunk d))
-                | cmd <- reverse cmds,
-                  d <- maybeToList . fmap infoProgDesc $ p cmd
+            tabulate (prefTabulateFill pprefs)
+              [ (pretty nm, align (extractChunk (infoProgDesc cmd)))
+              | (nm, cmd) <- reverse cmds
               ]
         _ -> mempty
 
@@ -112,19 +116,25 @@
 briefDesc' showOptional pprefs =
   wrapOver NoDefault MaybeRequired
     . foldTree pprefs style
-    . mfilterOptional
-    . treeMapParser (optDesc pprefs style)
+    . mFilterOptional
+    . treeMapParser optDesc'
   where
-    mfilterOptional
+    mFilterOptional
       | showOptional =
         id
       | otherwise =
         filterOptional
     style = OptDescStyle
-      { descSep = string "|",
+      { descSep = pretty '|',
         descHidden = False,
         descGlobal = False
       }
+    optDesc' reach opt =
+      let
+        (_, a, b) =
+          optDesc pprefs style reach opt
+      in
+        (a, b)
 
 -- | Wrap a doc in parentheses or brackets if required.
 wrapOver :: AltNodeType -> Parenthetic -> (Chunk Doc, Parenthetic) -> Chunk Doc
@@ -147,11 +157,11 @@
       x =
         foldr go mempty xs
       wrapLevel =
-        mult_wrap xs
+        multi_wrap xs
    in (x, wrapLevel)
   where
-    mult_wrap [_] = NeverRequired
-    mult_wrap _ = MaybeRequired
+    multi_wrap [_] = NeverRequired
+    multi_wrap _ = MaybeRequired
 foldTree prefs s (AltNode b xs) =
   (\x -> (x, NeverRequired))
     . fmap groupOrNestLine
@@ -188,23 +198,166 @@
 
 -- | Common generator for full descriptions and globals
 optionsDesc :: Bool -> ParserPrefs -> Parser a -> Chunk Doc
-optionsDesc global pprefs = tabulate . catMaybes . mapParser doc
+optionsDesc global pprefs p =
+  vsepChunks
+    . formatTitle'
+    . fmap tabulateGroup
+    . groupByTitle
+    $ docs
   where
+    docs :: [Maybe (OptGroup, (Doc, Doc))]
+    docs = mapParser doc p
+
+    groupByTitle :: [Maybe (OptGroup, (Doc, Doc))] -> [[(OptGroup, (Doc, Doc))]]
+    groupByTitle xs = groupFstAll . catMaybes $ xs
+
+    -- NOTE: [Nested group alignment]
+    --
+    -- For nested groups, we want to produce output like:
+    --
+    -- Group 1
+    --   --opt-1 INT          Option 1
+    --
+    -- - Group 2
+    --     --opt-2 INT        Option 2
+    --
+    --   - Group 3
+    --       - opt-3 INT      Option 3
+    --
+    -- That is, we have the following constraints:
+    --
+    --   1. Nested groups are prefixed with a hyphen '- ', where the hyphen
+    --     starts on the same column as the parent group.
+    --
+    --   2. We still want the listed options to be indented twice under the
+    --     group name, so this means nested options need to be indented
+    --     again by the standard amount (2), due to the hyphen.
+    --
+    --   3. Help text should be __globally__ aligned.
+
+    tabulateGroup :: [(OptGroup, (Doc, Doc))] -> (OptGroup, Chunk Doc)
+    tabulateGroup l@((title,_):_) =
+      (title, tabulate (prefTabulateFill pprefs) (getGroup <$> l))
+      where
+        -- Handle NOTE: [Nested group alignment] 3. here i.e. indent the
+        -- right Doc (help text) according to its indention level and
+        -- global maxGroupLevel. Notice there is an inverse relationship here,
+        -- as the further the entire group is indented, the less we need to
+        -- indent the help text.
+        getGroup :: (OptGroup, (Doc, Doc)) -> (Doc, Doc)
+        getGroup o@(_, (x, y)) =
+          let helpIndent = calcOptHelpIndent o
+          in (x, indent helpIndent y)
+
+        -- Indents the option help text, taking the option's group level and
+        -- maximum group level into account.
+        calcOptHelpIndent :: (OptGroup, a) -> Int
+        calcOptHelpIndent g =
+          let groupLvl = optGroupToLevel g
+          in lvlIndent * (maxGroupLevel - groupLvl)
+
+    tabulateGroup [] = (OptGroup [], mempty)
+
+    -- Fold so we can update the (printedGroups :: [String]) arg as we
+    -- iterate. End with a reverse since we use foldl'.
+    formatTitle' :: [(OptGroup, Chunk Doc)] -> [Chunk Doc]
+    formatTitle' = reverse . snd . foldl' formatTitle ([], [])
+
+    formatTitle :: ([String], [Chunk Doc]) -> (OptGroup, Chunk Doc) -> ([String], [Chunk Doc])
+    formatTitle (printedGroups, acc) o@(OptGroup groups, opts) =
+      case parentGroups of
+        -- No nested groups: No special logic.
+        [] -> (groupTitle : printedGroups, ((\d -> pretty groupTitle .$. d) <$> opts) : acc)
+        -- We have at least one parent group title P for current group G: P has
+        -- already been printed iff it is attached to another (non-grouped)
+        -- option. In other words, P has __not__ been printed if its only
+        -- member is another group.
+        --
+        -- The parameter (printedGroups :: [String]) holds all groups that
+        -- have already been printed.
+        parents@(_ : _) ->
+          let groupLvl = optGroupToLevel o
+              -- indent opts an extra lvlIndent to account for hyphen
+              indentOpts = indent lvlIndent
+
+              -- new printedGroups is all previous + this and parents.
+              printedGroups' = groupTitle : parents ++ printedGroups
+
+              parentsWithIndent = zip [0 .. ] parents
+
+              -- docs for unprinted parent title groups
+              parentDocs = pure $ mkParentDocs printedGroups parentsWithIndent
+
+              -- docs for the current group
+              thisDocs =
+                (\d -> lvlIndentNSub1 groupLvl $ (hyphenate groupTitle) .$. indentOpts d)
+                  <$> opts
+
+              allDocs = parentDocs <> thisDocs
+
+          in (printedGroups', allDocs : acc)
+      where
+        -- Separate parentGroups and _this_ group, in case we need to also
+        -- print parent groups.
+        (parentGroups, groupTitle) = case unsnoc groups of
+          Nothing -> ([], defTitle)
+          Just (parentGrps, grp) -> (parentGrps, grp)
+
+        defTitle =
+          if global
+            then "Global options:"
+            else "Available options:"
+
+    maxGroupLevel :: Int
+    maxGroupLevel = findMaxGroupLevel docs
+
+    -- Finds the maxium OptGroup level.
+    findMaxGroupLevel :: [Maybe (OptGroup, (Doc, Doc))] -> Int
+    findMaxGroupLevel = foldl' (\acc -> max acc . optGroupToLevel) 0 . catMaybes
+
+    optGroupToLevel :: (OptGroup, a) -> Int
+    -- 0 (defTitle) and 1 (custom group name) are handled identically
+    -- w.r.t indenation (not indented). Hence the subtraction here.
+    optGroupToLevel (OptGroup [], _) = 0
+    optGroupToLevel (OptGroup xs@(_ : _), _) = length xs - 1
+
+    doc :: ArgumentReachability -> Option a -> Maybe (OptGroup, (Doc, Doc))
     doc info opt = do
       guard . not . isEmpty $ n
       guard . not . isEmpty $ h
-      return (extractChunk n, align . extractChunk $ h <</>> hdef)
+      return (grp, (extractChunk n, align . extractChunk $ h <</>> hdef))
       where
-        n = fst $ optDesc pprefs style info opt
+        (grp, n, _) = optDesc pprefs style info opt
         h = optHelp opt
         hdef = Chunk . fmap show_def . optShowDefault $ opt
-        show_def s = parens (string "default:" <+> string s)
+        show_def s = parens (pretty "default:" <+> pretty s)
     style = OptDescStyle
-      { descSep = string ",",
+      { descSep = pretty ',',
         descHidden = True,
         descGlobal = global
       }
 
+    --
+    -- Prints all parent titles that have not already been printed
+    -- (i.e. in printedGroups).
+    mkParentDocs :: [String] -> [(Int, String)] -> Doc
+    mkParentDocs printedGroups =
+        foldr g mempty
+      where
+        g :: (Int, String) -> Doc ->  Doc
+        g (i, s) acc
+          | s `List.elem` printedGroups = acc
+          | i == 0 = pretty s .$. acc
+          | otherwise = lvlIndentNSub1 i $ hyphenate s .$. acc
+
+    hyphenate s = pretty ("- " <> s)
+
+    lvlIndentNSub1 :: Int -> Doc -> Doc
+    lvlIndentNSub1 n = indent (lvlIndent * (n - 1))
+
+    lvlIndent :: Int
+    lvlIndent = 2
+
 errorHelp :: Chunk Doc -> ParserHelp
 errorHelp chunk = mempty { helpError = chunk }
 
@@ -220,6 +373,9 @@
 usageHelp :: Chunk Doc -> ParserHelp
 usageHelp chunk = mempty { helpUsage = chunk }
 
+descriptionHelp :: Chunk Doc -> ParserHelp
+descriptionHelp chunk = mempty { helpDescription = chunk }
+
 bodyHelp :: Chunk Doc -> ParserHelp
 bodyHelp chunk = mempty { helpBody = chunk }
 
@@ -230,11 +386,11 @@
 parserHelp :: ParserPrefs -> Parser a -> ParserHelp
 parserHelp pprefs p =
   bodyHelp . vsepChunks $
-    with_title "Available options:" (fullDesc pprefs p)
+    fullDesc pprefs p
       : (group_title <$> cs)
   where
     def = "Available commands:"
-    cs = groupBy ((==) `on` fst) $ cmdDesc p
+    cs = groupFstAll $ cmdDesc pprefs p
 
     group_title a@((n, _) : _) =
       with_title (fromMaybe def n) $
@@ -242,25 +398,24 @@
     group_title _ = mempty
 
     with_title :: String -> Chunk Doc -> Chunk Doc
-    with_title title = fmap (string title .$.)
+    with_title title = fmap (pretty title .$.)
 
 
 parserGlobals :: ParserPrefs -> Parser a -> ParserHelp
 parserGlobals pprefs p =
-  globalsHelp $
-    (.$.) <$> stringChunk "Global options:"
-          <*> globalDesc pprefs p
+  globalsHelp $ globalDesc pprefs p
 
 
 
 -- | Generate option summary.
 parserUsage :: ParserPrefs -> Parser a -> String -> Doc
 parserUsage pprefs p progn =
-  hsep
-    [ string "Usage:",
-      string progn,
-      align (extractChunk (briefDesc pprefs p))
-    ]
+  group $
+    hsep
+      [ pretty "Usage:",
+        pretty progn,
+        hangAtIfOver 9 (prefBriefHangPoint pprefs) (extractChunk (briefDesc pprefs p))
+      ]
 
 -- | Peek at the structure of the rendered tree within.
 --
@@ -277,3 +432,55 @@
   | AlwaysRequired
   -- ^ Parenthesis should always be used.
   deriving (Eq, Ord, Show)
+
+-- | Groups on the first element of the tuple. This differs from the simple
+-- @groupBy ((==) `on` fst)@ in that non-adjacent groups are __also__ grouped
+-- together. For example:
+--
+-- @
+--   groupFst = groupBy ((==) `on` fst)
+--
+--   let xs = [(1, "a"), (1, "b"), (3, "c"), (2, "d"), (3, "e"), (2, "f")]
+--
+--   groupFst xs === [[(1,"a"),(1,"b")],[(3,"c")],[(2,"d")],[(3,"e")],[(2,"f")]]
+--   groupFstAll xs === [[(1,"a"),(1,"b")],[(3,"c"),(3,"e")],[(2,"d"),(2,"f")]]
+-- @
+--
+-- Notice that the original order is preserved i.e. we do not first sort on
+-- the first element.
+--
+-- @since 0.19.0.0
+groupFstAll :: Ord a => [(a, b)] -> [[(a, b)]]
+groupFstAll =
+  -- In order to group all (adjacent + non-adjacent) Eq elements together, we
+  -- sort the list so that the Eq elements are in fact adjacent, _then_ group.
+  -- We don't want to destroy the original order, however, so we add a
+  -- temporary index that maintains this original order. The full logic is:
+  --
+  -- 1. Add index i that preserves original order.
+  -- 2. Sort on tuple's fst.
+  -- 3. Group by fst.
+  -- 4. Sort by i, restoring original order.
+  -- 5. Drop index i.
+  fmap (NE.toList . dropIdx)
+    . List.sortOn toIdx
+    . NE.groupBy ((==) `on` fst')
+    . List.sortOn fst'
+    . zipWithIndex
+  where
+    dropIdx :: NonEmpty (Int, (a, b)) -> NonEmpty (a, b)
+    dropIdx = fmap snd
+
+    toIdx :: NonEmpty (Int, (a, b)) -> Int
+    toIdx ((x, _) :| _) = x
+
+    -- Like fst, ignores our added index
+    fst' :: (Int, (a, b)) -> a
+    fst' (_, (x, _)) = x
+
+    zipWithIndex :: [(a, b)] -> [(Int, (a, b))]
+    zipWithIndex = zip [1 ..]
+
+-- | From base-4.19.0.0.
+unsnoc :: [a] -> Maybe ([a], a)
+unsnoc = foldr (\x -> Just . maybe ([], x) (\(~(a, b)) -> (x : a, b))) Nothing
diff --git a/src/Options/Applicative/Help/Pretty.hs b/src/Options/Applicative/Help/Pretty.hs
--- a/src/Options/Applicative/Help/Pretty.hs
+++ b/src/Options/Applicative/Help/Pretty.hs
@@ -1,34 +1,64 @@
+{-# LANGUAGE CPP #-}
 module Options.Applicative.Help.Pretty
-  ( module Text.PrettyPrint.ANSI.Leijen
+  ( module Prettyprinter
+  , module Prettyprinter.Render.Terminal
+  , Doc
+  , SimpleDoc
+
   , (.$.)
+  , (</>)
+
   , groupOrNestLine
   , altSep
+  , hangAtIfOver
+
+  , prettyString
   ) where
 
-import           Control.Applicative
-import           Data.Semigroup ((<>))
+#if !MIN_VERSION_base(4,11,0)
+import           Data.Semigroup ((<>), mempty)
+#endif
+import qualified Data.Text.Lazy as Lazy
 
-import           Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>), columns)
-import           Text.PrettyPrint.ANSI.Leijen.Internal (Doc (..), flatten)
-import qualified Text.PrettyPrint.ANSI.Leijen as PP
+import           Prettyprinter hiding (Doc)
+import qualified Prettyprinter as PP
+import           Prettyprinter.Render.Terminal
 
 import           Prelude
 
-(.$.) :: Doc -> Doc -> Doc
-(.$.) = (PP.<$>)
+type Doc = PP.Doc AnsiStyle
+type SimpleDoc = SimpleDocStream AnsiStyle
 
+linebreak :: Doc
+linebreak = flatAlt line mempty
 
+(.$.) :: Doc -> Doc -> Doc
+x .$. y = x <> line <> y
+(</>) :: Doc -> Doc -> Doc
+x </> y = x <> softline <> y
+
 -- | Apply the function if we're not at the
 --   start of our nesting level.
 ifNotAtRoot :: (Doc -> Doc) -> Doc -> Doc
-ifNotAtRoot f doc =
-  Nesting $ \i ->
-    Column $ \j ->
-      if i == j
-        then doc
-        else f doc
+ifNotAtRoot =
+  ifElseAtRoot id
 
+-- | Apply the function if we're not at the
+--   start of our nesting level.
+ifAtRoot :: (Doc -> Doc) -> Doc -> Doc
+ifAtRoot =
+  flip ifElseAtRoot id
 
+-- | Apply the function if we're not at the
+--   start of our nesting level.
+ifElseAtRoot :: (Doc -> Doc) -> (Doc -> Doc) -> Doc -> Doc
+ifElseAtRoot f g doc =
+  nesting $ \i ->
+    column $ \j ->
+      if i == j
+        then f doc
+        else g doc
+
 -- | Render flattened text on this line, or start
 --   a new line before rendering any text.
 --
@@ -36,9 +66,7 @@
 --   group.
 groupOrNestLine :: Doc -> Doc
 groupOrNestLine =
-  Union
-    <$> flatten
-    <*> ifNotAtRoot (line <>) . nest 2
+  group . ifNotAtRoot (linebreak <>) . nest 2
 
 
 -- | Separate items in an alternative with a pipe.
@@ -53,4 +81,43 @@
 --   next line.
 altSep :: Doc -> Doc -> Doc
 altSep x y =
-  group (x <+> char '|' <> line) <//> y
+  group (x <+> pretty '|' <> line) <> group linebreak <>  y
+
+
+-- | Printer hacks to get nice indentation for long commands
+--   and subcommands.
+--
+--   If we're starting this section over the desired width
+--   (usually 1/3 of the ribbon), then we will make a line
+--   break, indent all of the usage, and go.
+--
+--   The ifAtRoot is an interesting clause. If this whole
+--   operation is put under a `group` then the linebreak
+--   will disappear; then item d will therefore not be at
+--   the starting column, and it won't be indented more.
+hangAtIfOver :: Int -> Int -> Doc -> Doc
+hangAtIfOver i j d =
+  column $ \k ->
+    if k <= j then
+      align d
+    else
+      linebreak <> ifAtRoot (indent i) d
+
+
+renderPretty :: Double -> Int -> Doc -> SimpleDocStream AnsiStyle
+renderPretty ribbonFraction lineWidth
+  = layoutPretty LayoutOptions
+      { layoutPageWidth = AvailablePerLine lineWidth ribbonFraction }
+
+prettyString :: Double -> Int -> Doc -> String
+prettyString ribbonFraction lineWidth
+  = streamToString
+  . renderPretty ribbonFraction lineWidth
+
+streamToString :: SimpleDocStream AnsiStyle -> String
+streamToString sdoc =
+  let
+    rendered =
+      Prettyprinter.Render.Terminal.renderLazy sdoc
+  in
+    Lazy.unpack rendered
diff --git a/src/Options/Applicative/Help/Types.hs b/src/Options/Applicative/Help/Types.hs
--- a/src/Options/Applicative/Help/Types.hs
+++ b/src/Options/Applicative/Help/Types.hs
@@ -14,30 +14,33 @@
   , helpSuggestions :: Chunk Doc
   , helpHeader :: Chunk Doc
   , helpUsage :: Chunk Doc
+  , helpDescription :: Chunk Doc
   , helpBody :: Chunk Doc
   , helpGlobals :: Chunk Doc
-  , helpFooter :: Chunk Doc }
+  , helpFooter :: Chunk Doc
+  }
 
 instance Show ParserHelp where
   showsPrec _ h = showString (renderHelp 80 h)
 
 instance Monoid ParserHelp where
-  mempty = ParserHelp mempty mempty mempty mempty mempty mempty mempty
+  mempty = ParserHelp mempty mempty mempty mempty mempty mempty mempty mempty
   mappend = (<>)
 
 instance Semigroup ParserHelp where
-  (ParserHelp e1 s1 h1 u1 b1 g1 f1) <> (ParserHelp e2 s2 h2 u2 b2 g2 f2)
+  (ParserHelp e1 s1 h1 u1 d1 b1 g1 f1) <> (ParserHelp e2 s2 h2 u2 d2 b2 g2 f2)
     = ParserHelp (mappend e1 e2) (mappend s1 s2)
                  (mappend h1 h2) (mappend u1 u2)
-                 (mappend b1 b2) (mappend g1 g2)
-                 (mappend f1 f2)
+                 (mappend d1 d2) (mappend b1 b2)
+                 (mappend g1 g2) (mappend f1 f2)
 
 helpText :: ParserHelp -> Doc
-helpText (ParserHelp e s h u b g f) = extractChunk . vsepChunks $ [e, s, h, u, b, g, f]
+helpText (ParserHelp e s h u d b g f) =
+  extractChunk $
+    vsepChunks [e, s, h, u, fmap (indent 2) d, b, g, f]
 
 -- | Convert a help text to 'String'.
 renderHelp :: Int -> ParserHelp -> String
 renderHelp cols
-  = (`displayS` "")
-  . renderPretty 1.0 cols
+  = prettyString 1.0 cols
   . helpText
diff --git a/src/Options/Applicative/Internal.hs b/src/Options/Applicative/Internal.hs
--- a/src/Options/Applicative/Internal.hs
+++ b/src/Options/Applicative/Internal.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE RankNTypes #-}
+
 module Options.Applicative.Internal
   ( P
   , MonadP(..)
@@ -18,11 +20,14 @@
   , ListT
   , takeListT
   , runListT
+  , hoistList
 
   , NondetT
   , cut
   , (<!>)
   , disamb
+
+  , mapParserOptions
   ) where
 
 import Control.Applicative
@@ -35,6 +40,7 @@
   (mapReaderT, runReader, runReaderT, Reader, ReaderT, ask)
 import Control.Monad.Trans.State (StateT, get, put, modify, evalStateT, runStateT)
 
+
 import Options.Applicative.Types
 
 class (Alternative m, MonadPlus m) => MonadP m where
@@ -172,9 +178,6 @@
 bimapTStep _ _ TNil = TNil
 bimapTStep f g (TCons a x) = TCons (f a) (g x)
 
-hoistList :: Monad m => [a] -> ListT m a
-hoistList = foldr (\x xt -> ListT (return (TCons x xt))) mzero
-
 takeListT :: Monad m => Int -> ListT m a -> ListT m a
 takeListT 0 = const mzero
 takeListT n = ListT . liftM (bimapTStep id (takeListT (n - 1))) . stepListT
@@ -192,7 +195,7 @@
          . stepListT
 
 instance Monad m => Applicative (ListT m) where
-  pure = hoistList . pure
+  pure a = ListT (return (TCons a mzero))
   (<*>) = ap
 
 instance Monad m => Monad (ListT m) where
@@ -263,3 +266,21 @@
   return $ case xs' of
     [x] -> Just x
     _   -> Nothing
+
+hoistList :: Alternative m => [a] -> m a
+hoistList = foldr cons empty
+  where
+    cons x xs = pure x <|> xs
+
+-- | Maps an Option modifying function over the Parser.
+--
+-- @since 0.19.0.0
+mapParserOptions :: (forall x. Option x -> Option x) -> Parser a -> Parser a
+mapParserOptions f = go
+  where
+    go :: forall y. Parser y -> Parser y
+    go (NilP x) = NilP x
+    go (OptP o) = OptP (f o)
+    go (MultP p1 p2) = MultP (go p1) (go p2)
+    go (AltP p1 p2) = AltP (go p1) (go p2)
+    go (BindP p1 p2) = BindP (go p1) (\x -> go (p2 x))
diff --git a/src/Options/Applicative/NonEmpty.hs b/src/Options/Applicative/NonEmpty.hs
--- a/src/Options/Applicative/NonEmpty.hs
+++ b/src/Options/Applicative/NonEmpty.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP, Rank2Types, ExistentialQuantification #-}
 module Options.Applicative.NonEmpty (
   some1
 ) where
diff --git a/src/Options/Applicative/Types.hs b/src/Options/Applicative/Types.hs
--- a/src/Options/Applicative/Types.hs
+++ b/src/Options/Applicative/Types.hs
@@ -11,6 +11,7 @@
 
   OptReader(..),
   OptProperties(..),
+  OptGroup(..),
   OptVisibility(..),
   Backtracking(..),
   ReadM(..),
@@ -125,7 +126,9 @@
                                   -- single space (default: False)
   , prefHelpShowGlobal :: Bool    -- ^ when displaying subparsers' usage help,
                                   -- show parent options under a "global options"
-                                  -- section (default: True)
+                                  -- section (default: False)
+  , prefTabulateFill ::Int        -- ^ Indentation width for tables
+  , prefBriefHangPoint :: Int     -- ^ Width at which to hang the brief description
   } deriving (Eq, Show)
 
 data OptName = OptShort !Char
@@ -146,6 +149,12 @@
   | Visible           -- ^ visible both in the full and brief descriptions
   deriving (Eq, Ord, Show)
 
+-- | Groups for optionals. Can be multiple in the case of nested groups.
+--
+-- @since 0.19.0.0
+newtype OptGroup = OptGroup [String]
+  deriving (Eq, Ord, Show)
+
 -- | Specification for an individual parser option.
 data OptProperties = OptProperties
   { propVisibility :: OptVisibility       -- ^ whether this flag is shown in the brief description
@@ -154,17 +163,23 @@
   , propShowDefault :: Maybe String       -- ^ what to show in the help text as the default
   , propShowGlobal :: Bool                -- ^ whether the option is presented in global options text
   , propDescMod :: Maybe ( Doc -> Doc )   -- ^ a function to run over the brief description
+  , propGroup :: OptGroup
+    -- ^ optional group(s)
+    --
+    -- @since 0.19.0.0
   }
 
 instance Show OptProperties where
-  showsPrec p (OptProperties pV pH pMV pSD pSG _)
+  showsPrec p (OptProperties pV pH pMV pSD pSG _ pGrp)
     = showParen (p >= 11)
     $ showString "OptProperties { propVisibility = " . shows pV
     . showString ", propHelp = " . shows pH
     . showString ", propMetaVar = " . shows pMV
     . showString ", propShowDefault = " . shows pSD
     . showString ", propShowGlobal = " . shows pSG
-    . showString ", propDescMod = _ }"
+    . showString ", propDescMod = _"
+    . showString ", propGroup = " . shows pGrp
+    . showString "}"
 
 -- | A single option of a parser.
 data Option a = Option
@@ -174,8 +189,9 @@
 
 data SomeParser = forall a . SomeParser (Parser a)
 
--- | Subparser context, containing the 'name' of the subparser and its parser info.
---   Used by parserFailure to display relevant usage information when parsing inside a subparser fails.
+-- | Subparser context, containing the name of the subparser and its parser info.
+--   Used by 'Options.Applicative.Extra.parserFailure' to display relevant usage
+--   information when parsing inside a subparser fails.
 data Context = forall a. Context String (ParserInfo a)
 
 instance Show (Option a) where
@@ -241,16 +257,16 @@
   -- ^ flag reader
   | ArgReader (CReader a)
   -- ^ argument reader
-  | CmdReader (Maybe String) [String] (String -> Maybe (ParserInfo a))
+  | CmdReader (Maybe String) [(String, ParserInfo a)]
   -- ^ command reader
 
 instance Functor OptReader where
   fmap f (OptReader ns cr e) = OptReader ns (fmap f cr) e
   fmap f (FlagReader ns x) = FlagReader ns (f x)
   fmap f (ArgReader cr) = ArgReader (fmap f cr)
-  fmap f (CmdReader n cs g) = CmdReader n cs ((fmap . fmap) f . g)
+  fmap f (CmdReader n cs) = CmdReader n ((fmap . fmap . fmap) f cs)
 
--- | A @Parser a@ is an option parser returning a value of type 'a'.
+-- | A @Parser a@ is an option parser returning a value of type @a@.
 data Parser a
   = NilP (Maybe a)
   | OptP (Option a)
@@ -341,7 +357,7 @@
   fmap f (ParserFailure err) = ParserFailure $ \progn ->
     let (h, exit, cols) = err progn in (f h, exit, cols)
 
--- | Result of 'execParserPure'.
+-- | Result of 'Options.Applicative.execParserPure'.
 data ParserResult a
   = Success a
   | Failure (ParserFailure ParserHelp)
diff --git a/tests/Examples/Cabal.hs b/tests/Examples/Cabal.hs
--- a/tests/Examples/Cabal.hs
+++ b/tests/Examples/Cabal.hs
@@ -39,10 +39,6 @@
   { buildDir :: FilePath }
   deriving Show
 
-version :: Parser (a -> a)
-version = infoOption "0.0.0"
-  (  long "version"
-  <> help "Print version information" )
 
 parser :: Parser Args
 parser = runA $ proc () -> do
@@ -60,7 +56,7 @@
            <> command "build"
               (info buildParser
                     (progDesc "Make this package ready for installation")) ) -< ()
-  A version >>> A helper -< Args opts cmds
+  A (simpleVersioner "0.0.0") >>> A helper -< Args opts cmds
 
 commonOpts :: Parser CommonOpts
 commonOpts = CommonOpts
diff --git a/tests/Examples/LongSub.hs b/tests/Examples/LongSub.hs
new file mode 100644
--- /dev/null
+++ b/tests/Examples/LongSub.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE CPP #-}
+module Examples.LongSub where
+
+import Data.Monoid
+import Options.Applicative
+
+#if __GLASGOW_HASKELL__ <= 702
+(<>) :: Monoid a => a -> a -> a
+(<>) = mappend
+#endif
+
+data Sample
+  = Hello [String]
+  | Goodbye
+  deriving (Eq, Show)
+
+hello :: Parser Sample
+hello =
+  Hello
+    <$> many (argument str (metavar "TARGET..."))
+    <*  switch (long "first-flag")
+    <*  switch (long "second-flag")
+    <*  switch (long "third-flag")
+    <*  switch (long "fourth-flag")
+
+sample :: Parser Sample
+sample = hsubparser
+       ( command "hello-very-long-sub"
+         (info hello
+               (progDesc "Print greeting"))
+       )
+
+opts :: ParserInfo Sample
+opts = info (sample <**> helper) idm
diff --git a/tests/Examples/ParserGroup/AllGrouped.hs b/tests/Examples/ParserGroup/AllGrouped.hs
new file mode 100644
--- /dev/null
+++ b/tests/Examples/ParserGroup/AllGrouped.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE NamedFieldPuns #-}
+
+module Examples.ParserGroup.AllGrouped (opts, main) where
+
+import Data.Semigroup ((<>))
+import Options.Applicative
+
+-- Tests the help page when every option belongs to some group i.e. there are
+-- no top-level options. Notice we put the helper (<**> helper) __inside__
+-- one of the groups, so that it is not a top-level option.
+--
+-- Also notice that although we add cmdParser to the same group, it is __not__
+-- rendered as part of this group. This is what we want, as it is an Argument
+-- and should not be rendered with the Options.
+
+data LogGroup = LogGroup
+  { logPath :: Maybe String,
+    logVerbosity :: Maybe Int
+  }
+  deriving (Show)
+
+data SystemGroup = SystemGroup
+  { poll :: Bool,
+    timeout :: Int
+  }
+  deriving (Show)
+
+data Sample = Sample
+  { logGroup :: LogGroup,
+    systemGroup :: SystemGroup,
+    cmd :: String
+  }
+  deriving (Show)
+
+sample :: Parser Sample
+sample =
+  Sample
+    <$> parseLogGroup
+    <*> parseSystemGroup
+    <*> parseCmd
+
+  where
+    parseLogGroup =
+      parserOptionGroup "Logging" $
+        LogGroup
+          <$> optional
+            ( strOption
+                ( long "file-log-path"
+                    <> metavar "PATH"
+                    <> help "Log file path"
+                )
+            )
+          <*> optional
+            ( option
+                auto
+                ( long "file-log-verbosity"
+                    <> metavar "INT"
+                    <> help "File log verbosity"
+                )
+            )
+            <**> helper
+
+    parseSystemGroup =
+      parserOptionGroup "System Options" $
+        SystemGroup
+          <$> switch
+            ( long "poll"
+                <> help "Whether to poll"
+            )
+          <*> option
+                auto
+                ( long "timeout"
+                    <> metavar "INT"
+                    <> help "Whether to time out"
+                )
+
+    parseCmd = argument str (metavar "Command")
+
+opts :: ParserInfo Sample
+opts =
+  info
+    sample
+    ( fullDesc
+        <> progDesc "Every option is grouped"
+        <> header "parser_group.all_grouped - a test for optparse-applicative"
+    )
+
+main :: IO ()
+main = do
+  r <- customExecParser (prefs helpShowGlobals) opts
+  print r
diff --git a/tests/Examples/ParserGroup/Basic.hs b/tests/Examples/ParserGroup/Basic.hs
new file mode 100644
--- /dev/null
+++ b/tests/Examples/ParserGroup/Basic.hs
@@ -0,0 +1,111 @@
+{-# LANGUAGE NamedFieldPuns #-}
+
+module Examples.ParserGroup.Basic (opts, main) where
+
+import Data.Semigroup ((<>))
+import Options.Applicative
+
+data LogGroup = LogGroup
+  { logPath :: Maybe String,
+    logVerbosity :: Maybe Int
+  }
+  deriving (Show)
+
+data SystemGroup = SystemGroup
+  { poll :: Bool,
+    timeout :: Int
+  }
+  deriving (Show)
+
+data Sample = Sample
+  { hello :: String,
+    logGroup :: LogGroup,
+    quiet :: Bool,
+    systemGroup :: SystemGroup,
+    verbosity :: Int,
+    cmd :: String
+  }
+  deriving (Show)
+
+sample :: Parser Sample
+sample =
+  Sample
+    <$> parseHello
+    <*> parseLogGroup
+    <*> parseQuiet
+    <*> parseSystemGroup
+    <*> parseVerbosity
+    <*> parseCmd
+
+  where
+    parseHello =
+      strOption
+        ( long "hello"
+            <> metavar "TARGET"
+            <> help "Target for the greeting"
+        )
+
+    parseLogGroup =
+      parserOptionGroup "Logging" $
+        LogGroup
+          <$> optional
+            ( strOption
+                ( long "file-log-path"
+                    <> metavar "PATH"
+                    <> help "Log file path"
+                )
+            )
+          <*> optional
+            ( option
+                auto
+                ( long "file-log-verbosity"
+                    <> metavar "INT"
+                    <> help "File log verbosity"
+                )
+            )
+
+    parseQuiet =
+      switch
+        ( long "quiet"
+            <> short 'q'
+            <> help "Whether to be quiet"
+        )
+
+    parseSystemGroup =
+      parserOptionGroup "System Options" $
+        SystemGroup
+          <$> switch
+            ( long "poll"
+                <> help "Whether to poll"
+            )
+          <*> ( option
+                  auto
+                  ( long "timeout"
+                      <> metavar "INT"
+                      <> help "Whether to time out"
+                  )
+              )
+
+    parseVerbosity =
+      option
+        auto
+        ( long "verbosity"
+            <> short 'v'
+            <> help "Console verbosity"
+        )
+
+    parseCmd = argument str (metavar "Command")
+
+opts :: ParserInfo Sample
+opts =
+  info
+    (sample <**> helper)
+    ( fullDesc
+        <> progDesc "Shows parser groups"
+        <> header "parser_group.basic - a test for optparse-applicative"
+    )
+
+main :: IO ()
+main = do
+  r <- customExecParser (prefs helpShowGlobals) opts
+  print r
diff --git a/tests/Examples/ParserGroup/CommandGroups.hs b/tests/Examples/ParserGroup/CommandGroups.hs
new file mode 100644
--- /dev/null
+++ b/tests/Examples/ParserGroup/CommandGroups.hs
@@ -0,0 +1,134 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Examples.ParserGroup.CommandGroups (opts, main) where
+
+import Data.Semigroup ((<>))
+import Options.Applicative
+
+data LogGroup = LogGroup
+  { logPath :: Maybe String,
+    logVerbosity :: Maybe Int
+  }
+  deriving (Show)
+
+data SystemGroup = SystemGroup
+  { poll :: Bool,
+    timeout :: Int
+  }
+  deriving (Show)
+
+data Command
+  = Delete
+  | List
+  | Print
+  | Query
+  deriving (Show)
+
+data Sample = Sample
+  { hello :: String,
+    logGroup :: LogGroup,
+    quiet :: Bool,
+    systemGroup :: SystemGroup,
+    verbosity :: Int,
+    cmd :: Command
+  }
+  deriving (Show)
+
+sample :: Parser Sample
+sample =
+  Sample
+    <$> parseHello
+    <*> parseLogGroup
+    <*> parseQuiet
+    <*> parseSystemGroup
+    <*> parseVerbosity
+    <*> parseCommand
+
+  where
+    parseHello =
+      strOption
+        ( long "hello"
+            <> metavar "TARGET"
+            <> help "Target for the greeting"
+        )
+
+    parseLogGroup =
+      parserOptionGroup "Logging" $
+        LogGroup
+          <$> optional
+            ( strOption
+                ( long "file-log-path"
+                    <> metavar "PATH"
+                    <> help "Log file path"
+                )
+            )
+          <*> optional
+            ( option
+                auto
+                ( long "file-log-verbosity"
+                    <> metavar "INT"
+                    <> help "File log verbosity"
+                )
+            )
+
+    parseQuiet =
+      switch
+        ( long "quiet"
+            <> short 'q'
+            <> help "Whether to be quiet"
+        )
+
+    parseSystemGroup =
+      parserOptionGroup "System Options" $
+        SystemGroup
+          <$> switch
+            ( long "poll"
+                <> help "Whether to poll"
+            )
+          <*> ( option
+                  auto
+                  ( long "timeout"
+                      <> metavar "INT"
+                      <> help "Whether to time out"
+                  )
+              )
+
+    parseVerbosity =
+      option
+        auto
+        ( long "verbosity"
+            <> short 'v'
+            <> help "Console verbosity"
+        )
+
+    parseCommand =
+      hsubparser
+        ( command "list 2" (info (pure List) $ progDesc "Lists elements")
+        )
+        <|> hsubparser
+        ( command "list" (info (pure List) $ progDesc "Lists elements")
+            <> command "print" (info (pure Print) $ progDesc "Prints table")
+            <> commandGroup "Info commands"
+        )
+        <|> hsubparser
+          ( command "delete" (info (pure Delete) $ progDesc "Deletes elements")
+          )
+        <|> hsubparser
+          ( command "query" (info (pure Query) $ progDesc "Runs a query")
+              <> commandGroup "Query commands"
+          )
+
+opts :: ParserInfo Sample
+opts =
+  info
+    (sample <**> helper)
+    ( fullDesc
+        <> progDesc "Option and command groups"
+        <> header "parser_group.command_groups - a test for optparse-applicative"
+    )
+
+main :: IO ()
+main = do
+  r <- customExecParser (prefs helpShowGlobals) opts
+  print r
diff --git a/tests/Examples/ParserGroup/DuplicateCommandGroups.hs b/tests/Examples/ParserGroup/DuplicateCommandGroups.hs
new file mode 100644
--- /dev/null
+++ b/tests/Examples/ParserGroup/DuplicateCommandGroups.hs
@@ -0,0 +1,92 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Examples.ParserGroup.DuplicateCommandGroups (opts, main) where
+
+import Data.Semigroup ((<>))
+import Options.Applicative
+
+-- This test demonstrates that duplicate + consecutive groups are merged,
+-- while duplicate + non-consecutive groups are not merged.
+
+data Command
+  = Delete
+  | Insert
+  | List
+  | Print
+  | Query
+  deriving (Show)
+
+data Sample = Sample
+  { hello :: String,
+    quiet :: Bool,
+    verbosity :: Int,
+    cmd :: Command
+  }
+  deriving (Show)
+
+sample :: Parser Sample
+sample =
+  Sample
+    <$> parseHello
+    <*> parseQuiet
+    <*> parseVerbosity
+    <*> parseCommand
+
+  where
+    parseHello =
+      strOption
+        ( long "hello"
+            <> metavar "TARGET"
+            <> help "Target for the greeting"
+        )
+
+    parseQuiet =
+      switch
+        ( long "quiet"
+            <> short 'q'
+            <> help "Whether to be quiet"
+        )
+
+    parseVerbosity =
+      option
+        auto
+        ( long "verbosity"
+            <> short 'v'
+            <> help "Console verbosity"
+        )
+
+    parseCommand =
+      hsubparser
+        ( command "list" (info (pure List) $ progDesc "Lists elements")
+            <> commandGroup "Info commands"
+        )
+        <|> hsubparser
+          ( command "delete" (info (pure Delete) $ progDesc "Deletes elements")
+              <> commandGroup "Update commands"
+          )
+        <|> hsubparser
+          ( command "insert" (info (pure Insert) $ progDesc "Inserts elements")
+              <> commandGroup "Update commands"
+          )
+        <|> hsubparser
+          ( command "query" (info (pure Query) $ progDesc "Runs a query")
+          )
+        <|> hsubparser
+        ( command "print" (info (pure Print) $ progDesc "Prints table")
+            <> commandGroup "Info commands"
+        )
+
+opts :: ParserInfo Sample
+opts =
+  info
+    (sample <**> helper)
+    ( fullDesc
+        <> progDesc "Duplicate consecutive command groups consolidated"
+        <> header "parser_group.duplicate_command_groups - a test for optparse-applicative"
+    )
+
+main :: IO ()
+main = do
+  r <- customExecParser (prefs helpShowGlobals) opts
+  print r
diff --git a/tests/Examples/ParserGroup/Duplicates.hs b/tests/Examples/ParserGroup/Duplicates.hs
new file mode 100644
--- /dev/null
+++ b/tests/Examples/ParserGroup/Duplicates.hs
@@ -0,0 +1,153 @@
+{-# LANGUAGE NamedFieldPuns #-}
+
+module Examples.ParserGroup.Duplicates (opts, main) where
+
+import Data.Semigroup ((<>))
+import Options.Applicative
+
+-- NOTE: This is the same structure as ParserGroup.Basic __except__
+-- we have two (non-consecutive) "Logging" groups and two (consecutive)
+-- System groups. This test demonstrates two things:
+--
+-- 1. Non-consecutive groups are not merged (i.e. we display two "Logging"
+--    sections).
+-- 2. Consecutive groups are merged (i.e. we display only one "System" group).
+--
+-- This is like command groups.
+
+data LogGroup1 = LogGroup1
+  { logPath :: Maybe String,
+    logVerbosity :: Maybe Int
+  }
+  deriving (Show)
+
+data LogGroup2 = LogGroup2
+  { logNamespace :: String
+  }
+  deriving (Show)
+
+data SystemGroup1 = SystemGroup1
+  { poll :: Bool,
+    timeout :: Int
+  }
+  deriving (Show)
+
+newtype SystemGroup2 = SystemGroup2
+  { sysFlag :: Bool
+  }
+  deriving (Show)
+
+data Sample = Sample
+  { hello :: String,
+    logGroup1 :: LogGroup1,
+    quiet :: Bool,
+    systemGroup1 :: SystemGroup1,
+    systemGroup2 :: SystemGroup2,
+    logGroup2 :: LogGroup2,
+    verbosity :: Int,
+    cmd :: String
+  }
+  deriving (Show)
+
+sample :: Parser Sample
+sample =
+  Sample
+    <$> parseHello
+    <*> parseLogGroup1
+    <*> parseQuiet
+    <*> parseSystemGroup1
+    <*> parseSystemGroup2
+    <*> parseLogGroup2
+    <*> parseVerbosity
+    <*> parseCmd
+
+  where
+    parseHello =
+      strOption
+        ( long "hello"
+            <> metavar "TARGET"
+            <> help "Target for the greeting"
+        )
+
+    parseLogGroup1 =
+      parserOptionGroup "Logging" $
+        LogGroup1
+          <$> optional
+            ( strOption
+                ( long "file-log-path"
+                    <> metavar "PATH"
+                    <> help "Log file path"
+                )
+            )
+          <*> optional
+            ( option
+                auto
+                ( long "file-log-verbosity"
+                    <> metavar "INT"
+                    <> help "File log verbosity"
+                )
+            )
+
+    parseQuiet =
+      switch
+        ( long "quiet"
+            <> short 'q'
+            <> help "Whether to be quiet"
+        )
+
+    parseSystemGroup1 =
+      parserOptionGroup "System" $
+        SystemGroup1
+          <$> switch
+            ( long "poll"
+                <> help "Whether to poll"
+            )
+          <*> option
+                auto
+                ( long "timeout"
+                    <> metavar "INT"
+                    <> help "Whether to time out"
+                )
+
+    parseSystemGroup2 =
+      parserOptionGroup "System" $
+        SystemGroup2
+          <$> switch
+            ( long "sysFlag"
+                <> help "Some flag"
+            )
+
+    parseLogGroup2 =
+      parserOptionGroup "Logging" $
+        LogGroup2
+            <$>
+              strOption
+                ( long "log-namespace"
+                    <> metavar "STR"
+                    <> help "Log namespace"
+                )
+
+    parseVerbosity =
+      option
+        auto
+        ( long "verbosity"
+            <> short 'v'
+            <> help "Console verbosity"
+        )
+
+    parseCmd = argument str (metavar "Command")
+
+opts :: ParserInfo Sample
+opts =
+  info
+    (sample <**> helper)
+    ( fullDesc
+        <> progDesc "Duplicate consecutive groups consolidated"
+        <> header "parser_group.duplicates - a test for optparse-applicative"
+    )
+
+main :: IO ()
+main = do
+  r <- customExecParser (prefs helpShowGlobals) opts
+  print r
+
diff --git a/tests/Examples/ParserGroup/Nested.hs b/tests/Examples/ParserGroup/Nested.hs
new file mode 100644
--- /dev/null
+++ b/tests/Examples/ParserGroup/Nested.hs
@@ -0,0 +1,139 @@
+{-# LANGUAGE NamedFieldPuns #-}
+
+module Examples.ParserGroup.Nested (opts, main) where
+
+import Data.Semigroup ((<>))
+import Options.Applicative
+
+-- Nested groups. Demonstrates that group can nest.
+
+data LogGroup = LogGroup
+  { logPath :: Maybe String,
+    systemGroup :: SystemGroup,
+    logVerbosity :: Maybe Int
+  }
+  deriving (Show)
+
+data SystemGroup = SystemGroup
+  { poll :: Bool,
+    deepNested :: Nested2,
+    timeout :: Int
+  }
+  deriving (Show)
+
+data Nested2 = Nested2
+  { nested2Str :: String,
+    nested3 :: Nested3
+  }
+  deriving (Show)
+
+newtype Nested3 = Nested3
+  { nested3Str :: String
+  }
+  deriving (Show)
+
+data Sample = Sample
+  { hello :: String,
+    logGroup :: LogGroup,
+    quiet :: Bool,
+    verbosity :: Int,
+    group2 :: (Int, Int),
+    cmd :: String
+  }
+  deriving (Show)
+
+sample :: Parser Sample
+sample =
+  Sample
+    <$> parseHello
+    <*> parseLogGroup
+    <*> parseQuiet
+    <*> parseVerbosity
+    <*> parseGroup2
+    <*> parseCmd
+
+  where
+    parseHello =
+      strOption
+        ( long "hello"
+            <> metavar "TARGET"
+            <> help "Target for the greeting"
+        )
+
+    parseLogGroup =
+      parserOptionGroup "First group" $
+      parserOptionGroup "Second group" $
+      parserOptionGroup "Logging" $
+        LogGroup
+          <$> parseLogPath
+          <*> parseSystemGroup
+          <*> parseLogVerbosity
+
+      where
+        parseLogPath =
+          optional
+            ( strOption
+                ( long "file-log-path"
+                    <> metavar "PATH"
+                    <> help "Log file path"
+                )
+            )
+        parseLogVerbosity =
+          optional
+            ( option
+                auto
+                ( long "file-log-verbosity"
+                    <> metavar "INT"
+                    <> help "File log verbosity"
+                )
+            )
+
+    parseQuiet =
+      switch
+        ( long "quiet"
+            <> short 'q'
+            <> help "Whether to be quiet"
+        )
+
+    parseSystemGroup =
+      parserOptionGroup "System Options" $
+        SystemGroup
+          <$> switch (long "poll" <> help "Whether to poll")
+          <*> parseNested2
+          <*> option auto (long "timeout" <> metavar "INT" <> help "Whether to time out")
+
+    parseNested2 =
+      parserOptionGroup "Nested2" $
+        Nested2
+          <$> option auto (long "double-nested" <> metavar "STR" <> help "Some nested option")
+          <*> parseNested3
+
+    parseNested3 =
+      parserOptionGroup "Nested3" $
+        Nested3 <$> option auto (long "triple-nested" <> metavar "STR" <> help "Another option")
+
+    parseGroup2 :: Parser (Int, Int)
+    parseGroup2 = parserOptionGroup "Group 2" $
+      (,)
+        <$> parserOptionGroup "G 2.1" (option auto (long "one" <> help "Option 1"))
+        <*> parserOptionGroup "G 2.2" (option auto (long "two" <> help "Option 2"))
+
+    parseVerbosity =
+      option auto (long "verbosity" <> short 'v' <> help "Console verbosity")
+
+    parseCmd =
+      argument str (metavar "Command")
+
+opts :: ParserInfo Sample
+opts =
+  info
+    (sample <**> helper)
+    ( fullDesc
+        <> progDesc "Nested parser groups"
+        <> header "parser_group.nested - a test for optparse-applicative"
+    )
+
+main :: IO ()
+main = do
+  r <- customExecParser (prefs helpShowGlobals) opts
+  print r
diff --git a/tests/cabal.err.txt b/tests/cabal.err.txt
--- a/tests/cabal.err.txt
+++ b/tests/cabal.err.txt
@@ -1,4 +1,5 @@
 Usage: cabal configure [--enable-tests] [-f|--flags FLAGS]
+
   Prepare to build the package
 
 Available options:
@@ -8,4 +9,4 @@
 
 Global options:
   -v,--verbose LEVEL       Set verbosity to LEVEL
-  --version                Print version information
+  --version                Show version information
diff --git a/tests/formatting-long-subcommand.err.txt b/tests/formatting-long-subcommand.err.txt
new file mode 100644
--- /dev/null
+++ b/tests/formatting-long-subcommand.err.txt
@@ -0,0 +1,9 @@
+Usage: formatting-long-subcommand hello-very-long-sub 
+         [TARGET...] [--first-flag] 
+         [--second-flag] [--third-flag] 
+         [--fourth-flag]
+
+  Print greeting
+
+Available options:
+  -h,--help                Show this help text
diff --git a/tests/formatting.err.txt b/tests/formatting.err.txt
--- a/tests/formatting.err.txt
+++ b/tests/formatting.err.txt
@@ -1,4 +1,5 @@
 Usage: formatting [-t|--test FOO_BAR_BAZ_LONG_METAVARIABLE]
+
   This is a very long program description. This
   text should be automatically wrapped to fit the
   size of the terminal
diff --git a/tests/hello.err.txt b/tests/hello.err.txt
--- a/tests/hello.err.txt
+++ b/tests/hello.err.txt
@@ -1,6 +1,7 @@
 hello - a test for optparse-applicative
 
 Usage: hello --hello TARGET [-q|--quiet] [--repeat INT]
+
   Print a greeting for TARGET
 
 Available options:
diff --git a/tests/parser_group_all_grouped.err.txt b/tests/parser_group_all_grouped.err.txt
new file mode 100644
--- /dev/null
+++ b/tests/parser_group_all_grouped.err.txt
@@ -0,0 +1,16 @@
+parser_group.all_grouped - a test for optparse-applicative
+
+Usage: parser_group_all_grouped [--file-log-path PATH] 
+                                [--file-log-verbosity INT] [--poll]
+                                --timeout INT Command
+
+  Every option is grouped
+
+Logging
+  --file-log-path PATH     Log file path
+  --file-log-verbosity INT File log verbosity
+  -h,--help                Show this help text
+
+System Options
+  --poll                   Whether to poll
+  --timeout INT            Whether to time out
diff --git a/tests/parser_group_basic.err.txt b/tests/parser_group_basic.err.txt
new file mode 100644
--- /dev/null
+++ b/tests/parser_group_basic.err.txt
@@ -0,0 +1,21 @@
+parser_group.basic - a test for optparse-applicative
+
+Usage: parser_group_basic --hello TARGET [--file-log-path PATH] 
+                          [--file-log-verbosity INT] [-q|--quiet] [--poll]
+                          --timeout INT (-v|--verbosity ARG) Command
+
+  Shows parser groups
+
+Available options:
+  --hello TARGET           Target for the greeting
+  -q,--quiet               Whether to be quiet
+  -v,--verbosity ARG       Console verbosity
+  -h,--help                Show this help text
+
+Logging
+  --file-log-path PATH     Log file path
+  --file-log-verbosity INT File log verbosity
+
+System Options
+  --poll                   Whether to poll
+  --timeout INT            Whether to time out
diff --git a/tests/parser_group_command_groups.err.txt b/tests/parser_group_command_groups.err.txt
new file mode 100644
--- /dev/null
+++ b/tests/parser_group_command_groups.err.txt
@@ -0,0 +1,33 @@
+parser_group.command_groups - a test for optparse-applicative
+
+Usage: parser_group_command_groups --hello TARGET [--file-log-path PATH] 
+                                   [--file-log-verbosity INT] [-q|--quiet] 
+                                   [--poll] --timeout INT (-v|--verbosity ARG) 
+                                   (COMMAND | COMMAND | COMMAND | COMMAND)
+
+  Option and command groups
+
+Available options:
+  --hello TARGET           Target for the greeting
+  -q,--quiet               Whether to be quiet
+  -v,--verbosity ARG       Console verbosity
+  -h,--help                Show this help text
+
+Logging
+  --file-log-path PATH     Log file path
+  --file-log-verbosity INT File log verbosity
+
+System Options
+  --poll                   Whether to poll
+  --timeout INT            Whether to time out
+
+Available commands:
+  list 2                   Lists elements
+  delete                   Deletes elements
+
+Info commands
+  list                     Lists elements
+  print                    Prints table
+
+Query commands
+  query                    Runs a query
diff --git a/tests/parser_group_duplicate_command_groups.err.txt b/tests/parser_group_duplicate_command_groups.err.txt
new file mode 100644
--- /dev/null
+++ b/tests/parser_group_duplicate_command_groups.err.txt
@@ -0,0 +1,24 @@
+parser_group.duplicate_command_groups - a test for optparse-applicative
+
+Usage: parser_group_duplicate_command_groups 
+         --hello TARGET [-q|--quiet] (-v|--verbosity ARG) 
+         (COMMAND | COMMAND | COMMAND | COMMAND | COMMAND)
+
+  Duplicate consecutive command groups consolidated
+
+Available options:
+  --hello TARGET           Target for the greeting
+  -q,--quiet               Whether to be quiet
+  -v,--verbosity ARG       Console verbosity
+  -h,--help                Show this help text
+
+Available commands:
+  query                    Runs a query
+
+Info commands
+  list                     Lists elements
+  print                    Prints table
+
+Update commands
+  delete                   Deletes elements
+  insert                   Inserts elements
diff --git a/tests/parser_group_duplicates.err.txt b/tests/parser_group_duplicates.err.txt
new file mode 100644
--- /dev/null
+++ b/tests/parser_group_duplicates.err.txt
@@ -0,0 +1,24 @@
+parser_group.duplicates - a test for optparse-applicative
+
+Usage: parser_group_duplicates --hello TARGET [--file-log-path PATH] 
+                               [--file-log-verbosity INT] [-q|--quiet] [--poll]
+                               --timeout INT [--sysFlag] --log-namespace STR
+                               (-v|--verbosity ARG) Command
+
+  Duplicate consecutive groups consolidated
+
+Available options:
+  --hello TARGET           Target for the greeting
+  -q,--quiet               Whether to be quiet
+  -v,--verbosity ARG       Console verbosity
+  -h,--help                Show this help text
+
+Logging
+  --file-log-path PATH     Log file path
+  --file-log-verbosity INT File log verbosity
+  --log-namespace STR      Log namespace
+
+System
+  --poll                   Whether to poll
+  --timeout INT            Whether to time out
+  --sysFlag                Some flag
diff --git a/tests/parser_group_nested.err.txt b/tests/parser_group_nested.err.txt
new file mode 100644
--- /dev/null
+++ b/tests/parser_group_nested.err.txt
@@ -0,0 +1,37 @@
+parser_group.nested - a test for optparse-applicative
+
+Usage: parser_group_nested --hello TARGET [--file-log-path PATH] [--poll]
+                           --double-nested STR --triple-nested STR --timeout INT
+                           [--file-log-verbosity INT] [-q|--quiet]
+                           (-v|--verbosity ARG) --one ARG --two ARG Command
+
+  Nested parser groups
+
+Available options:
+  --hello TARGET                     Target for the greeting
+  -q,--quiet                         Whether to be quiet
+  -v,--verbosity ARG                 Console verbosity
+  -h,--help                          Show this help text
+
+First group
+- Second group
+  - Logging
+      --file-log-path PATH           Log file path
+      --file-log-verbosity INT       File log verbosity
+
+    - System Options
+        --poll                       Whether to poll
+        --timeout INT                Whether to time out
+
+      - Nested2
+          --double-nested STR        Some nested option
+
+        - Nested3
+            --triple-nested STR      Another option
+
+Group 2
+- G 2.1
+    --one ARG                        Option 1
+
+- G 2.2
+    --two ARG                        Option 2
diff --git a/tests/test.hs b/tests/test.hs
--- a/tests/test.hs
+++ b/tests/test.hs
@@ -9,9 +9,17 @@
 import qualified Examples.Cabal as Cabal
 import qualified Examples.Alternatives as Alternatives
 import qualified Examples.Formatting as Formatting
+import qualified Examples.LongSub as LongSub
+import qualified Examples.ParserGroup.AllGrouped as ParserGroup.AllGrouped
+import qualified Examples.ParserGroup.Basic as ParserGroup.Basic
+import qualified Examples.ParserGroup.CommandGroups as ParserGroup.CommandGroups
+import qualified Examples.ParserGroup.DuplicateCommandGroups as ParserGroup.DuplicateCommandGroups
+import qualified Examples.ParserGroup.Duplicates as ParserGroup.Duplicates
+import qualified Examples.ParserGroup.Nested as ParserGroup.Nested
 
 import           Control.Applicative
 import           Control.Monad
+import           Data.Function (on)
 import           Data.List hiding (group)
 import           Data.List.NonEmpty (NonEmpty ((:|)))
 import           Data.Semigroup hiding (option)
@@ -27,7 +35,7 @@
 
 
 import qualified Options.Applicative.Help as H
-import           Options.Applicative.Help.Pretty (Doc, SimpleDoc(..))
+import           Options.Applicative.Help.Pretty (Doc)
 import qualified Options.Applicative.Help.Pretty as Doc
 import           Options.Applicative.Help.Chunk
 import           Options.Applicative.Help.Levenshtein
@@ -317,6 +325,49 @@
       result = execParserPure (prefs disambiguate) i ["--ba"]
   in  assertError result (\_ -> property succeeded)
 
+
+prop_disambiguate_in_same_subparsers :: Property
+prop_disambiguate_in_same_subparsers = once $
+  let p0 = subparser (command "oranges" (info (pure "oranges") idm) <> command "apples" (info (pure "apples") idm) <> metavar "B")
+      i = info (p0 <**> helper) idm
+      result = execParserPure (prefs disambiguate) i ["orang"]
+  in  assertResult result ((===) "oranges")
+
+prop_disambiguate_commands_in_separate_subparsers :: Property
+prop_disambiguate_commands_in_separate_subparsers = once $
+  let p2 = subparser (command "oranges" (info (pure "oranges") idm)  <> metavar "B")
+      p1 = subparser (command "apples" (info (pure "apples") idm)  <> metavar "C")
+      p0 = p1 <|> p2
+      i = info (p0 <**> helper) idm
+      result = execParserPure (prefs disambiguate) i ["orang"]
+  in  assertResult result ((===) "oranges")
+
+prop_fail_ambiguous_commands_in_same_subparser :: Property
+prop_fail_ambiguous_commands_in_same_subparser = once $
+  let p0 = subparser (command "oranges" (info (pure ()) idm) <> command "orangutans" (info (pure ()) idm) <> metavar "B")
+      i = info (p0 <**> helper) idm
+      result = execParserPure (prefs disambiguate) i ["orang"]
+  in  assertError result (\_ -> property succeeded)
+
+prop_fail_ambiguous_commands_in_separate_subparser :: Property
+prop_fail_ambiguous_commands_in_separate_subparser = once $
+  let p2 = subparser (command "oranges" (info (pure ()) idm)  <> metavar "B")
+      p1 = subparser (command "orangutans" (info (pure ()) idm)  <> metavar "C")
+      p0 = p1 <|> p2
+      i = info (p0 <**> helper) idm
+      result = execParserPure (prefs disambiguate) i ["orang"]
+  in  assertError result (\_ -> property succeeded)
+
+prop_without_disambiguation_same_named_commands_should_parse_in_order :: Property
+prop_without_disambiguation_same_named_commands_should_parse_in_order = once $
+  let p3 = subparser (command "b" (info (pure ()) idm)  <> metavar "B")
+      p2 = subparser (command "a" (info (pure ()) idm)  <> metavar "B")
+      p1 = subparser (command "a" (info (pure ()) idm)  <> metavar "C")
+      p0 = (,,) <$> p1 <*> p2 <*> p3
+      i = info (p0 <**> helper) idm
+      result = execParserPure defaultPrefs i ["b", "a", "a"]
+  in  assertResult result ((===) ((), (), ()))
+
 prop_completion :: Property
 prop_completion = once . ioProperty $
   let p = (,)
@@ -891,16 +942,50 @@
     post = run i ["--help", "not-a-command"]
   in grabHelpMessage pre === grabHelpMessage post
 
+
+prop_long_command_line_flow :: Property
+prop_long_command_line_flow = once $
+  let p = LongSub.sample <**> helper
+      i = info p
+        ( progDesc (concat
+            [ "This is a very long program description. "
+            , "This text should be automatically wrapped "
+            , "to fit the size of the terminal" ]) )
+  in checkHelpTextWith ExitSuccess (prefs (columns 50)) "formatting-long-subcommand" i ["hello-very-long-sub", "--help"]
+
+prop_parser_group_basic :: Property
+prop_parser_group_basic = once $
+  checkHelpText "parser_group_basic" ParserGroup.Basic.opts ["--help"]
+
+prop_parser_group_command_groups :: Property
+prop_parser_group_command_groups = once $
+  checkHelpText "parser_group_command_groups" ParserGroup.CommandGroups.opts ["--help"]
+
+prop_parser_group_duplicate_command_groups :: Property
+prop_parser_group_duplicate_command_groups = once $
+  checkHelpText "parser_group_duplicate_command_groups" ParserGroup.DuplicateCommandGroups.opts ["--help"]
+
+prop_parser_group_duplicates :: Property
+prop_parser_group_duplicates = once $
+  checkHelpText "parser_group_duplicates" ParserGroup.Duplicates.opts ["--help"]
+
+prop_parser_group_all_grouped :: Property
+prop_parser_group_all_grouped = once $
+  checkHelpText "parser_group_all_grouped" ParserGroup.AllGrouped.opts ["--help"]
+
+prop_parser_group_nested :: Property
+prop_parser_group_nested = once $
+  checkHelpText "parser_group_nested" ParserGroup.Nested.opts ["--help"]
+
 ---
 
 deriving instance Arbitrary a => Arbitrary (Chunk a)
-deriving instance Eq SimpleDoc
-deriving instance Show SimpleDoc
 
-equalDocs :: Float -> Int -> Doc -> Doc -> Property
-equalDocs f w d1 d2 = Doc.renderPretty f w d1
-                  === Doc.renderPretty f w d2
 
+equalDocs :: Double -> Int -> Doc -> Doc -> Property
+equalDocs f w d1 d2 = Doc.prettyString f w d1
+                  === Doc.prettyString f w d2
+
 prop_listToChunk_1 :: [String] -> Property
 prop_listToChunk_1 xs = isEmpty (listToChunk xs) === null xs
 
@@ -913,10 +998,10 @@
 prop_extractChunk_2 :: Chunk String -> Property
 prop_extractChunk_2 x = extractChunk (fmap pure x) === x
 
-prop_stringChunk_1 :: Positive Float -> Positive Int -> String -> Property
+prop_stringChunk_1 :: Positive Double -> Positive Int -> String -> Property
 prop_stringChunk_1 (Positive f) (Positive w) s =
   equalDocs f w (extractChunk (stringChunk s))
-                (Doc.string s)
+                (Doc.pretty s)
 
 prop_stringChunk_2 :: String -> Property
 prop_stringChunk_2 s = isEmpty (stringChunk s) === null s
