diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa2e785f..bb577994 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@
         * All ``cmd2`` code should be ported to use the new ``argparse``-based decorators
         * See the [Argument Processing](http://cmd2.readthedocs.io/en/latest/argument_processing.html) section of the documentation for more information on these decorators
         * Alternatively, see the [argparse_example.py](https://github.com/python-cmd2/cmd2/blob/master/examples/argparse_example.py)
+    * Deleted ``cmd_with_subs_completer``, ``get_subcommands``, and ``get_subcommand_completer``
+        * Replaced by default AutoCompleter implementation for all commands using argparse
 * Python 2 no longer supported
     * ``cmd2`` now supports Python 3.4+
 
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index 35f9342b..e87e9c04 100755
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -71,6 +71,10 @@ import re as _re
 from .rl_utils import rl_force_redisplay
 
 
+# attribute that can optionally added to an argparse argument (called an Action) to
+# define the completion choices for the argument. You may provide a Collection or a Function.
+ACTION_ARG_CHOICES = 'arg_choices'
+
 class _RangeAction(object):
     def __init__(self, nargs: Union[int, str, Tuple[int, int], None]):
         self.nargs_min = None
@@ -220,6 +224,10 @@ class AutoCompleter(object):
             # if there are choices defined, record them in the arguments dictionary
             if action.choices is not None:
                 self._arg_choices[action.dest] = action.choices
+            # if completion choices are tagged on the action, record them
+            elif hasattr(action, ACTION_ARG_CHOICES):
+                action_arg_choices = getattr(action, ACTION_ARG_CHOICES)
+                self._arg_choices[action.dest] = action_arg_choices
 
             # if the parameter is flag based, it will have option_strings
             if action.option_strings:
@@ -406,6 +414,21 @@ class AutoCompleter(object):
 
         return completion_results
 
+    def complete_command_help(self, tokens: List[str], text: str, line: str, begidx: int, endidx: int) -> List[str]:
+        """Supports the completion of sub-commands for commands through the cmd2 help command."""
+        for idx, token in enumerate(tokens):
+            if idx >= self._token_start_index:
+                if self._positional_completers:
+                    # For now argparse only allows 1 sub-command group per level
+                    # so this will only loop once.
+                    for completers in self._positional_completers.values():
+                        if token in completers:
+                            return completers[token].complete_command_help(tokens, text, line, begidx, endidx)
+                        else:
+                            return self.basic_complete(text, line, begidx, endidx, completers.keys())
+        return []
+
+
     @staticmethod
     def _process_action_nargs(action: argparse.Action, arg_state: _ArgumentState) -> None:
         if isinstance(action, _RangeAction):
@@ -467,6 +490,7 @@ class AutoCompleter(object):
     def _resolve_choices_for_arg(self, action: argparse.Action, used_values=()) -> List[str]:
         if action.dest in self._arg_choices:
             args = self._arg_choices[action.dest]
+
             if callable(args):
                 args = args()
 
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 60d1dbf8..288a506b 100755
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -51,6 +51,7 @@ import pyperclip
 
 # Set up readline
 from .rl_utils import rl_force_redisplay, readline, rl_type, RlType
+from .argparse_completer import AutoCompleter, ACArgumentParser
 
 if rl_type == RlType.PYREADLINE:
 
@@ -266,23 +267,8 @@ def with_argparser_and_unknown_args(argparser: argparse.ArgumentParser) -> Calla
 
         cmd_wrapper.__doc__ = argparser.format_help()
 
-        # Mark this function as having an argparse ArgumentParser (used by do_help)
-        cmd_wrapper.__dict__['has_parser'] = True
-
-        # If there are subcommands, store their names in a list to support tab-completion of subcommand names
-        if argparser._subparsers is not None:
-            # Key is subcommand name and value is completer function
-            subcommands = collections.OrderedDict()
-
-            # Get all subcommands and check if they have completer functions
-            for name, parser in argparser._subparsers._group_actions[0]._name_parser_map.items():
-                if 'completer' in parser._defaults:
-                    completer = parser._defaults['completer']
-                else:
-                    completer = None
-                subcommands[name] = completer
-
-            cmd_wrapper.__dict__['subcommands'] = subcommands
+        # Mark this function as having an argparse ArgumentParser
+        setattr(cmd_wrapper, 'argparser', argparser)
 
         return cmd_wrapper
 
@@ -318,24 +304,8 @@ def with_argparser(argparser: argparse.ArgumentParser) -> Callable:
 
         cmd_wrapper.__doc__ = argparser.format_help()
 
-        # Mark this function as having an argparse ArgumentParser (used by do_help)
-        cmd_wrapper.__dict__['has_parser'] = True
-
-        # If there are subcommands, store their names in a list to support tab-completion of subcommand names
-        if argparser._subparsers is not None:
-
-            # Key is subcommand name and value is completer function
-            subcommands = collections.OrderedDict()
-
-            # Get all subcommands and check if they have completer functions
-            for name, parser in argparser._subparsers._group_actions[0]._name_parser_map.items():
-                if 'completer' in parser._defaults:
-                    completer = parser._defaults['completer']
-                else:
-                    completer = None
-                subcommands[name] = completer
-
-            cmd_wrapper.__dict__['subcommands'] = subcommands
+        # Mark this function as having an argparse ArgumentParser
+        setattr(cmd_wrapper, 'argparser', argparser)
 
         return cmd_wrapper
 
@@ -1020,49 +990,6 @@ class Cmd(cmd.Cmd):
             return self._colorcodes[color][True] + val + self._colorcodes[color][False]
         return val
 
-    def get_subcommands(self, command):
-        """
-        Returns a list of a command's subcommand names if they exist
-        :param command: the command we are querying
-        :return: A subcommand list or None
-        """
-
-        subcommand_names = None
-
-        # Check if is a valid command
-        funcname = self._func_named(command)
-
-        if funcname:
-            # Check to see if this function was decorated with an argparse ArgumentParser
-            func = getattr(self, funcname)
-            subcommands = func.__dict__.get('subcommands', None)
-            if subcommands is not None:
-                subcommand_names = subcommands.keys()
-
-        return subcommand_names
-
-    def get_subcommand_completer(self, command, subcommand):
-        """
-        Returns a subcommand's tab completion function if one exists
-        :param command: command which owns the subcommand
-        :param subcommand: the subcommand we are querying
-        :return: A completer or None
-        """
-
-        completer = None
-
-        # Check if is a valid command
-        funcname = self._func_named(command)
-
-        if funcname:
-            # Check to see if this function was decorated with an argparse ArgumentParser
-            func = getattr(self, funcname)
-            subcommands = func.__dict__.get('subcommands', None)
-            if subcommands is not None:
-                completer = subcommands[subcommand]
-
-        return completer
-
     # -----  Methods related to tab completion -----
 
     def set_completion_defaults(self):
@@ -1794,16 +1721,14 @@ class Cmd(cmd.Cmd):
                     try:
                         compfunc = getattr(self, 'complete_' + command)
                     except AttributeError:
-                        compfunc = self.completedefault
-
-                    subcommands = self.get_subcommands(command)
-                    if subcommands is not None:
-                        # Since there are subcommands, then try completing those if the cursor is in
-                        # the token at index 1, otherwise default to using compfunc
-                        index_dict = {1: subcommands}
-                        compfunc = functools.partial(self.index_based_complete,
-                                                     index_dict=index_dict,
-                                                     all_else=compfunc)
+                        # There's no completer function, next see if the command uses argparser
+                        try:
+                            cmd_func = getattr(self, 'do_' + command)
+                            argparser = getattr(cmd_func, 'argparser')
+                            # Command uses argparser, switch to the default argparse completer
+                            compfunc = functools.partial(self._autocomplete_default, argparser=argparser)
+                        except AttributeError:
+                            compfunc = self.completedefault
 
                 # A valid command was not entered
                 else:
@@ -1910,6 +1835,16 @@ class Cmd(cmd.Cmd):
         except IndexError:
             return None
 
+    def _autocomplete_default(self, text: str, line: str, begidx: int, endidx: int,
+                              argparser: argparse.ArgumentParser) -> List[str]:
+        """Default completion function for argparse commands."""
+        completer = AutoCompleter(argparser)
+
+        tokens, _ = self.tokens_for_completion(line, begidx, endidx)
+        results = completer.complete_command(tokens, text, line, begidx, endidx)
+
+        return results
+
     def get_all_commands(self):
         """
         Returns a list of all commands
@@ -1964,12 +1899,15 @@ class Cmd(cmd.Cmd):
             strs_to_match = list(topics | visible_commands)
             matches = self.basic_complete(text, line, begidx, endidx, strs_to_match)
 
-        # Check if we are completing a subcommand
-        elif index == subcmd_index:
-
-            # Match subcommands if any exist
-            command = tokens[cmd_index]
-            matches = self.basic_complete(text, line, begidx, endidx, self.get_subcommands(command))
+        # check if the command uses argparser
+        elif index >= subcmd_index:
+            try:
+                cmd_func = getattr(self, 'do_' + tokens[cmd_index])
+                parser = getattr(cmd_func, 'argparser')
+                completer = AutoCompleter(parser)
+                matches = completer.complete_command_help(tokens[1:], text, line, begidx, endidx)
+            except AttributeError:
+                pass
 
         return matches
 
@@ -2620,7 +2558,7 @@ Usage:  Usage: unalias [-a] name [name ...]
             if funcname:
                 # Check to see if this function was decorated with an argparse ArgumentParser
                 func = getattr(self, funcname)
-                if func.__dict__.get('has_parser', False):
+                if hasattr(func, 'argparser'):
                     # Function has an argparser, so get help based on all the arguments in case there are sub-commands
                     new_arglist = arglist[1:]
                     new_arglist.append('-h')
@@ -2843,10 +2781,10 @@ Usage:  Usage: unalias [-a] name [name ...]
         else:
             raise LookupError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
 
-    set_parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
+    set_parser = ACArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
     set_parser.add_argument('-a', '--all', action='store_true', help='display read-only settings as well')
     set_parser.add_argument('-l', '--long', action='store_true', help='describe function of parameter')
-    set_parser.add_argument('settable', nargs='*', help='[param_name] [value]')
+    set_parser.add_argument('settable', nargs=(0,2), help='[param_name] [value]')
 
     @with_argparser(set_parser)
     def do_set(self, args):
@@ -2927,87 +2865,6 @@ Usage:  Usage: unalias [-a] name [name ...]
         index_dict = {1: self.shell_cmd_complete}
         return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
 
-    def cmd_with_subs_completer(self, text, line, begidx, endidx):
-        """
-        This is a function provided for convenience to those who want an easy way to add
-        tab completion to functions that implement subcommands. By setting this as the
-        completer of the base command function, the correct completer for the chosen subcommand
-        will be called.
-
-        The use of this function requires assigning a completer function to the subcommand's parser
-        Example:
-            A command called print has a subcommands called 'names' that needs a tab completer
-            When you create the parser for names, include the completer function in the parser's defaults.
-
-            names_parser.set_defaults(func=print_names, completer=complete_print_names)
-
-            To make sure the names completer gets called, set the completer for the print function
-            in a similar fashion to what follows.
-
-            complete_print = cmd2.Cmd.cmd_with_subs_completer
-
-        When the subcommand's completer is called, this function will have stripped off all content from the
-        beginning of the command line before the subcommand, meaning the line parameter always starts with the
-        subcommand name and the index parameters reflect this change.
-
-        For instance, the command "print names -d 2" becomes "names -d 2"
-        begidx and endidx are incremented accordingly
-
-        :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
-        :return: List[str] - a list of possible tab completions
-        """
-        # The command is the token at index 0 in the command line
-        cmd_index = 0
-
-        # The subcommand is the token at index 1 in the command line
-        subcmd_index = 1
-
-        # Get all tokens through the one being completed
-        tokens, _ = self.tokens_for_completion(line, begidx, endidx)
-        if tokens is None:
-            return []
-
-        matches = []
-
-        # Get the index of the token being completed
-        index = len(tokens) - 1
-
-        # If the token being completed is past the subcommand name, then do subcommand specific tab-completion
-        if index > subcmd_index:
-
-            # Get the command name
-            command = tokens[cmd_index]
-
-            # Get the subcommand name
-            subcommand = tokens[subcmd_index]
-
-            # Find the offset into line where the subcommand name begins
-            subcmd_start = 0
-            for cur_index in range(0, subcmd_index + 1):
-                cur_token = tokens[cur_index]
-                subcmd_start = line.find(cur_token, subcmd_start)
-
-                if cur_index != subcmd_index:
-                    subcmd_start += len(cur_token)
-
-            # Strip off everything before subcommand name
-            orig_line = line
-            line = line[subcmd_start:]
-
-            # Update the indexes
-            diff = len(orig_line) - len(line)
-            begidx -= diff
-            endidx -= diff
-
-            # Call the subcommand specific completer if it exists
-            compfunc = self.get_subcommand_completer(command, subcommand)
-            if compfunc is not None:
-                matches = compfunc(self, text, line, begidx, endidx)
-
-        return matches
 
     # noinspection PyBroadException
     def do_py(self, arg):
diff --git a/docs/argument_processing.rst b/docs/argument_processing.rst
index 183dde4e..ecf59504 100644
--- a/docs/argument_processing.rst
+++ b/docs/argument_processing.rst
@@ -346,12 +346,10 @@ Sub-commands
 Sub-commands are supported for commands using either the ``@with_argparser`` or
 ``@with_argparser_and_unknown_args`` decorator.  The syntax for supporting them is based on argparse sub-parsers.
 
-Also, a convenience function called ``cmd_with_subs_completer`` is available to easily add tab completion to functions
-that implement subcommands. By setting this as the completer of the base command function, the correct completer for
-the chosen subcommand will be called.
+You may add multiple layers of sub-commands for your command. Cmd2 will automatically traverse and tab-complete
+sub-commands for all commands using argparse.
 
-See the subcommands_ example to learn more about how to use sub-commands in your ``cmd2`` application.
-This example also demonstrates usage of ``cmd_with_subs_completer``. In addition, the docstring for
-``cmd_with_subs_completer`` offers more details.
+See the subcommands_ and tab_autocompletion_ example to learn more about how to use sub-commands in your ``cmd2`` application.
 
 .. _subcommands: https://github.com/python-cmd2/cmd2/blob/master/examples/subcommands.py
+.. _tab_autocompletion: https://github.com/python-cmd2/cmd2/blob/master/examples/tab_autocompletion.py
diff --git a/examples/subcommands.py b/examples/subcommands.py
index 031b17b2..75c0733e 100755
--- a/examples/subcommands.py
+++ b/examples/subcommands.py
@@ -35,12 +35,6 @@ class SubcommandsExample(cmd2.Cmd):
         """sport subcommand of base command"""
         self.poutput('Sport is {}'.format(args.sport))
 
-    # noinspection PyUnusedLocal
-    def complete_base_sport(self, text, line, begidx, endidx):
-        """ Adds tab completion to base sport subcommand """
-        index_dict = {1: sport_item_strs}
-        return self.index_based_complete(text, line, begidx, endidx, index_dict)
-
     # create the top-level parser for the base command
     base_parser = argparse.ArgumentParser(prog='base')
     base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
@@ -53,15 +47,22 @@ class SubcommandsExample(cmd2.Cmd):
 
     # create the parser for the "bar" subcommand
     parser_bar = base_subparsers.add_parser('bar', help='bar help')
-    parser_bar.add_argument('z', help='string')
     parser_bar.set_defaults(func=base_bar)
 
+    bar_subparsers = parser_bar.add_subparsers(title='layer3', help='help for 3rd layer of commands')
+    parser_bar.add_argument('z', help='string')
+
+    bar_subparsers.add_parser('apple', help='apple help')
+    bar_subparsers.add_parser('artichoke', help='artichoke help')
+    bar_subparsers.add_parser('cranberries', help='cranberries help')
+
     # create the parser for the "sport" subcommand
     parser_sport = base_subparsers.add_parser('sport', help='sport help')
-    parser_sport.add_argument('sport', help='Enter name of a sport')
+    sport_arg = parser_sport.add_argument('sport', help='Enter name of a sport')
+    setattr(sport_arg, 'arg_choices', sport_item_strs)
 
     # Set both a function and tab completer for the "sport" subcommand
-    parser_sport.set_defaults(func=base_sport, completer=complete_base_sport)
+    parser_sport.set_defaults(func=base_sport)
 
     @with_argparser(base_parser)
     def do_base(self, args):
@@ -74,9 +75,6 @@ class SubcommandsExample(cmd2.Cmd):
             # No subcommand was provided, so call help
             self.do_help('base')
 
-    # Enable tab completion of base to make sure the subcommands' completers get called.
-    complete_base = cmd2.Cmd.cmd_with_subs_completer
-
 
 if __name__ == '__main__':
     app = SubcommandsExample()
diff --git a/examples/tab_autocompletion.py b/examples/tab_autocompletion.py
index c704908f..17c8391d 100755
--- a/examples/tab_autocompletion.py
+++ b/examples/tab_autocompletion.py
@@ -13,6 +13,15 @@ from typing import List
 import cmd2
 from cmd2 import with_argparser, with_category, argparse_completer
 
+actors = ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher', 'Alec Guinness', 'Peter Mayhew',
+          'Anthony Daniels', 'Adam Driver', 'Daisy Ridley', 'John Boyega', 'Oscar Isaac',
+          'Lupita Nyong\'o', 'Andy Serkis', 'Liam Neeson', 'Ewan McGregor', 'Natalie Portman',
+          'Jake Lloyd', 'Hayden Christensen', 'Christopher Lee']
+
+def query_actors() -> List[str]:
+    """Simulating a function that queries and returns a completion values"""
+    return actors
+
 
 class TabCompleteExample(cmd2.Cmd):
     """ Example cmd2 application where we a base command which has a couple subcommands."""
@@ -27,10 +36,6 @@ class TabCompleteExample(cmd2.Cmd):
     show_ratings = ['TV-Y', 'TV-Y7', 'TV-G', 'TV-PG', 'TV-14', 'TV-MA']
     static_list_directors = ['J. J. Abrams', 'Irvin Kershner', 'George Lucas', 'Richard Marquand',
                              'Rian Johnson', 'Gareth Edwards']
-    actors = ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher', 'Alec Guinness', 'Peter Mayhew',
-              'Anthony Daniels', 'Adam Driver', 'Daisy Ridley', 'John Boyega', 'Oscar Isaac',
-              'Lupita Nyong\'o', 'Andy Serkis', 'Liam Neeson', 'Ewan McGregor', 'Natalie Portman',
-              'Jake Lloyd', 'Hayden Christensen', 'Christopher Lee']
     USER_MOVIE_LIBRARY = ['ROGUE1', 'SW_EP04', 'SW_EP05']
     MOVIE_DATABASE_IDS = ['SW_EP01', 'SW_EP02', 'SW_EP03', 'ROGUE1', 'SW_EP04',
                           'SW_EP05', 'SW_EP06', 'SW_EP07', 'SW_EP08', 'SW_EP09']
@@ -115,15 +120,6 @@ class TabCompleteExample(cmd2.Cmd):
         if not args.type:
             self.do_help('suggest')
 
-    def complete_suggest(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
-        """ Adds tab completion to media"""
-        completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser, 1)
-
-        tokens, _ = self.tokens_for_completion(line, begidx, endidx)
-        results = completer.complete_command(tokens, text, line, begidx, endidx)
-
-        return results
-
     # If you prefer the original argparse help output but would like narg ranges, it's possible
     # to enable narg ranges without the help changes using this method
 
@@ -143,15 +139,6 @@ class TabCompleteExample(cmd2.Cmd):
         if not args.type:
             self.do_help('orig_suggest')
 
-    def complete_hybrid_suggest(self, text, line, begidx, endidx):
-        """ Adds tab completion to media"""
-        completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser_hybrid)
-
-        tokens, _ = self.tokens_for_completion(line, begidx, endidx)
-        results = completer.complete_command(tokens, text, line, begidx, endidx)
-
-        return results
-
     # This variant demonstrates the AutoCompleter working with the orginial argparse.
     # Base argparse is unable to specify narg ranges. Autocompleter will keep expecting additional arguments
     # for the -d/--duration flag until you specify a new flaw or end the list it with '--'
@@ -170,23 +157,98 @@ class TabCompleteExample(cmd2.Cmd):
         if not args.type:
             self.do_help('orig_suggest')
 
-    def complete_orig_suggest(self, text, line, begidx, endidx) -> List[str]:
-        """ Adds tab completion to media"""
-        completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser_orig)
+    ###################################################################################
+    # The media command demonstrates a completer with multiple layers of subcommands
+    #   - This example demonstrates how to tag a completion attribute on each action, enabling argument
+    #       completion without implementing a complete_COMMAND function
 
-        tokens, _ = self.tokens_for_completion(line, begidx, endidx)
-        results = completer.complete_command(tokens, text, line, begidx, endidx)
+    def _do_vid_media_movies(self, args) -> None:
+        if not args.command:
+            self.do_help('media movies')
+        elif args.command == 'list':
+            for movie_id in TabCompleteExample.MOVIE_DATABASE:
+                movie = TabCompleteExample.MOVIE_DATABASE[movie_id]
+                print('{}\n-----------------------------\n{}   ID: {}\nDirector: {}\nCast:\n    {}\n\n'
+                      .format(movie['title'], movie['rating'], movie_id,
+                              ', '.join(movie['director']),
+                              '\n    '.join(movie['actor'])))
 
-        return results
+    def _do_vid_media_shows(self, args) -> None:
+        if not args.command:
+            self.do_help('media shows')
+
+        elif args.command == 'list':
+            for show_id in TabCompleteExample.SHOW_DATABASE:
+                show = TabCompleteExample.SHOW_DATABASE[show_id]
+                print('{}\n-----------------------------\n{}   ID: {}'
+                      .format(show['title'], show['rating'], show_id))
+                for season in show['seasons']:
+                    ep_list = show['seasons'][season]
+                    print('  Season {}:\n    {}'
+                          .format(season,
+                                  '\n    '.join(ep_list)))
+                print()
+
+    video_parser = argparse_completer.ACArgumentParser(prog='media')
+
+    video_types_subparsers = video_parser.add_subparsers(title='Media Types', dest='type')
+
+    vid_movies_parser = video_types_subparsers.add_parser('movies')
+    vid_movies_parser.set_defaults(func=_do_vid_media_movies)
+
+    vid_movies_commands_subparsers = vid_movies_parser.add_subparsers(title='Commands', dest='command')
+
+    vid_movies_list_parser = vid_movies_commands_subparsers.add_parser('list')
+
+    vid_movies_list_parser.add_argument('-t', '--title', help='Title Filter')
+    vid_movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+',
+                                        choices=ratings_types)
+    # save a reference to the action object
+    director_action = vid_movies_list_parser.add_argument('-d', '--director', help='Director Filter')
+    actor_action = vid_movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append')
+
+    # tag the action objects with completion providers. This can be a collection or a callable
+    setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors)
+    setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors)
+
+    vid_movies_add_parser = vid_movies_commands_subparsers.add_parser('add')
+    vid_movies_add_parser.add_argument('title', help='Movie Title')
+    vid_movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types)
+
+    # save a reference to the action object
+    director_action = vid_movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2),
+                                                         required=True)
+    actor_action = vid_movies_add_parser.add_argument('actor', help='Actors', nargs='*')
+
+    # tag the action objects with completion providers. This can be a collection or a callable
+    setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors)
+    setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors)
+
+    vid_movies_delete_parser = vid_movies_commands_subparsers.add_parser('delete')
+
+    vid_shows_parser = video_types_subparsers.add_parser('shows')
+    vid_shows_parser.set_defaults(func=_do_vid_media_shows)
+
+    vid_shows_commands_subparsers = vid_shows_parser.add_subparsers(title='Commands', dest='command')
+
+    vid_shows_list_parser = vid_shows_commands_subparsers.add_parser('list')
+
+    @with_category(CAT_AUTOCOMPLETE)
+    @with_argparser(video_parser)
+    def do_video(self, args):
+        """Video management command demonstrates multiple layers of subcommands being handled by AutoCompleter"""
+        func = getattr(args, 'func', None)
+        if func is not None:
+            # Call whatever subcommand function was selected
+            func(self, args)
+        else:
+            # No subcommand was provided, so call help
+            self.do_help('video')
 
     ###################################################################################
     # The media command demonstrates a completer with multiple layers of subcommands
     #   - This example uses a flat completion lookup dictionary
 
-    def query_actors(self) -> List[str]:
-        """Simulating a function that queries and returns a completion values"""
-        return TabCompleteExample.actors
-
     def _do_media_movies(self, args) -> None:
         if not args.command:
             self.do_help('media movies')
@@ -264,7 +326,7 @@ class TabCompleteExample(cmd2.Cmd):
     # name collisions.
     def complete_media(self, text, line, begidx, endidx):
         """ Adds tab completion to media"""
-        choices = {'actor': self.query_actors,  # function
+        choices = {'actor': query_actors,  # function
                    'director': TabCompleteExample.static_list_directors  # static list
                    }
         completer = argparse_completer.AutoCompleter(TabCompleteExample.media_parser, arg_choices=choices)

argparse tab autocompleter
I'm working on a tab completion implementation that automatically generates completions based on an argparse definition. Creating this issue to enable discussions.
diff --git a/tests/test_autocompletion.py b/tests/test_autocompletion.py
index e68bc104..1d0c9678 100644
--- a/tests/test_autocompletion.py
+++ b/tests/test_autocompletion.py
@@ -213,6 +213,27 @@ def test_autocomp_subcmd_flag_comp_list(cmd2_app):
     assert first_match is not None and first_match == '"Gareth Edwards'
 
 
+def test_autocomp_subcmd_flag_comp_func_attr(cmd2_app):
+    text = 'A'
+    line = 'video movies list -a "{}'.format(text)
+    endidx = len(line)
+    begidx = endidx - len(text)
+
+    first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
+    assert first_match is not None and \
+           cmd2_app.completion_matches == ['Adam Driver', 'Alec Guinness', 'Andy Serkis', 'Anthony Daniels']
+
+
+def test_autocomp_subcmd_flag_comp_list_attr(cmd2_app):
+    text = 'G'
+    line = 'video movies list -d {}'.format(text)
+    endidx = len(line)
+    begidx = endidx - len(text)
+
+    first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
+    assert first_match is not None and first_match == '"Gareth Edwards'
+
+
 def test_autcomp_pos_consumed(cmd2_app):
     text = ''
     line = 'library movie add SW_EP01 {}'.format(text)
@@ -254,3 +275,5 @@ def test_autcomp_custom_func_list_and_dict_arg(cmd2_app):
     first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
     assert first_match is not None and \
            cmd2_app.completion_matches == ['S01E02', 'S01E03', 'S02E01', 'S02E03']
+
+
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 35ef4c0f..48f50bdc 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -68,8 +68,12 @@ def test_base_argparse_help(base_app, capsys):
 def test_base_invalid_option(base_app, capsys):
     run_cmd(base_app, 'set -z')
     out, err = capsys.readouterr()
-    expected = ['usage: set [-h] [-a] [-l] [settable [settable ...]]', 'set: error: unrecognized arguments: -z']
-    assert normalize(str(err)) == expected
+    out = normalize(out)
+    err = normalize(err)
+    assert len(err) == 3
+    assert len(out) == 15
+    assert 'Error: unrecognized arguments: -z' in err[0]
+    assert out[0] == 'usage: set [-h] [-a] [-l] [settable [settable ...]]'
 
 def test_base_shortcuts(base_app):
     out = run_cmd(base_app, 'shortcuts')
diff --git a/tests/test_completion.py b/tests/test_completion.py
index a01d1166..cf45f281 100644
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -14,7 +14,8 @@ import sys
 
 import cmd2
 import pytest
-from .conftest import complete_tester
+from .conftest import complete_tester, StdOut
+from examples.subcommands import SubcommandsExample
 
 # List of strings used with completion functions
 food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato']
@@ -726,76 +727,13 @@ def test_add_opening_quote_delimited_space_in_prefix(cmd2_app):
            os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix and \
            cmd2_app.display_matches == expected_display
 
-class SubcommandsExample(cmd2.Cmd):
-    """
-    Example cmd2 application where we a base command which has a couple subcommands
-    and the "sport" subcommand has tab completion enabled.
-    """
-
-    def __init__(self):
-        cmd2.Cmd.__init__(self)
-
-    # subcommand functions for the base command
-    def base_foo(self, args):
-        """foo subcommand of base command"""
-        self.poutput(args.x * args.y)
-
-    def base_bar(self, args):
-        """bar subcommand of base command"""
-        self.poutput('((%s))' % args.z)
-
-    def base_sport(self, args):
-        """sport subcommand of base command"""
-        self.poutput('Sport is {}'.format(args.sport))
-
-    # noinspection PyUnusedLocal
-    def complete_base_sport(self, text, line, begidx, endidx):
-        """ Adds tab completion to base sport subcommand """
-        index_dict = {1: sport_item_strs}
-        return self.index_based_complete(text, line, begidx, endidx, index_dict)
-
-    # create the top-level parser for the base command
-    base_parser = argparse.ArgumentParser(prog='base')
-    base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
-
-    # create the parser for the "foo" subcommand
-    parser_foo = base_subparsers.add_parser('foo', help='foo help')
-    parser_foo.add_argument('-x', type=int, default=1, help='integer')
-    parser_foo.add_argument('y', type=float, help='float')
-    parser_foo.set_defaults(func=base_foo)
-
-    # create the parser for the "bar" subcommand
-    parser_bar = base_subparsers.add_parser('bar', help='bar help')
-    parser_bar.add_argument('z', help='string')
-    parser_bar.set_defaults(func=base_bar)
-
-    # create the parser for the "sport" subcommand
-    parser_sport = base_subparsers.add_parser('sport', help='sport help')
-    parser_sport.add_argument('sport', help='Enter name of a sport')
-
-    # Set both a function and tab completer for the "sport" subcommand
-    parser_sport.set_defaults(func=base_sport, completer=complete_base_sport)
-
-    @cmd2.with_argparser(base_parser)
-    def do_base(self, args):
-        """Base command help"""
-        func = getattr(args, 'func', None)
-        if func is not None:
-            # Call whatever subcommand function was selected
-            func(self, args)
-        else:
-            # No subcommand was provided, so call help
-            self.do_help('base')
-
-    # Enable tab completion of base to make sure the subcommands' completers get called.
-    complete_base = cmd2.Cmd.cmd_with_subs_completer
-
 
 @pytest.fixture
 def sc_app():
-    app = SubcommandsExample()
-    return app
+    c = SubcommandsExample()
+    c.stdout = StdOut()
 
+    return c
 
 def test_cmd2_subcommand_completion_single_end(sc_app):
     text = 'f'
@@ -913,12 +851,6 @@ class SubcommandsWithUnknownExample(cmd2.Cmd):
         """sport subcommand of base command"""
         self.poutput('Sport is {}'.format(args.sport))
 
-    # noinspection PyUnusedLocal
-    def complete_base_sport(self, text, line, begidx, endidx):
-        """ Adds tab completion to base sport subcommand """
-        index_dict = {1: sport_item_strs}
-        return self.index_based_complete(text, line, begidx, endidx, index_dict)
-
     # create the top-level parser for the base command
     base_parser = argparse.ArgumentParser(prog='base')
     base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
@@ -936,10 +868,8 @@ class SubcommandsWithUnknownExample(cmd2.Cmd):
 
     # create the parser for the "sport" subcommand
     parser_sport = base_subparsers.add_parser('sport', help='sport help')
-    parser_sport.add_argument('sport', help='Enter name of a sport')
-
-    # Set both a function and tab completer for the "sport" subcommand
-    parser_sport.set_defaults(func=base_sport, completer=complete_base_sport)
+    sport_arg = parser_sport.add_argument('sport', help='Enter name of a sport')
+    setattr(sport_arg, 'arg_choices', sport_item_strs)
 
     @cmd2.with_argparser_and_unknown_args(base_parser)
     def do_base(self, args):
@@ -952,9 +882,6 @@ class SubcommandsWithUnknownExample(cmd2.Cmd):
             # No subcommand was provided, so call help
             self.do_help('base')
 
-    # Enable tab completion of base to make sure the subcommands' completers get called.
-    complete_base = cmd2.Cmd.cmd_with_subs_completer
-
 
 @pytest.fixture
 def scu_app():
@@ -971,6 +898,8 @@ def test_cmd2_subcmd_with_unknown_completion_single_end(scu_app):
 
     first_match = complete_tester(text, line, begidx, endidx, scu_app)
 
+    print('first_match: {}'.format(first_match))
+
     # It is at end of line, so extra space is present
     assert first_match is not None and scu_app.completion_matches == ['foo ']
 

pip install -e .[dev]
parse_log_pytest
pytest
pytest pytest-cov pytest-mock
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
certifi==2021.5.30
-e git+https://github.com/python-cmd2/cmd2.git@a0a46f9396a72f440f65e46d7170a0d366796574#egg=cmd2
colorama==0.4.5
coverage==6.2
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pyperclip==1.9.0
pytest==6.2.4
pytest-cov==4.0.0
pytest-mock==3.6.1
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
tomli==1.2.3
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
wcwidth==0.2.13
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: cmd2
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - colorama==0.4.5
      - coverage==6.2
      - pyperclip==1.9.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - tomli==1.2.3
      - wcwidth==0.2.13
prefix: /opt/conda/envs/cmd2

tests/test_autocompletion.py::test_autocomp_subcmd_flag_comp_func_attr tests/test_autocompletion.py::test_autocomp_subcmd_flag_comp_list_attr tests/test_cmd2.py::test_base_invalid_option tests/test_completion.py::test_subcommand_tab_completion tests/test_completion.py::test_subcommand_tab_completion_space_in_text
tests/test_cmd2.py::test_output_redirection tests/test_cmd2.py::test_interrupt_quit tests/test_cmd2.py::test_interrupt_noquit tests/test_cmd2.py::test_which_editor_good
tests/test_autocompletion.py::test_help_required_group tests/test_autocompletion.py::test_help_required_group_long tests/test_autocompletion.py::test_autocomp_flags tests/test_autocompletion.py::test_autcomp_hint tests/test_autocompletion.py::test_autcomp_flag_comp tests/test_autocompletion.py::test_autocomp_flags_choices tests/test_autocompletion.py::test_autcomp_hint_in_narg_range tests/test_autocompletion.py::test_autocomp_flags_narg_max tests/test_autocompletion.py::test_autcomp_narg_beyond_max tests/test_autocompletion.py::test_autocomp_subcmd_nested tests/test_autocompletion.py::test_autocomp_subcmd_flag_choices_append tests/test_autocompletion.py::test_autocomp_subcmd_flag_choices_append_exclude tests/test_autocompletion.py::test_autocomp_subcmd_flag_comp_func tests/test_autocompletion.py::test_autocomp_subcmd_flag_comp_list tests/test_autocompletion.py::test_autcomp_pos_consumed tests/test_autocompletion.py::test_autcomp_pos_after_flag tests/test_autocompletion.py::test_autcomp_custom_func_list_arg tests/test_autocompletion.py::test_autcomp_custom_func_list_and_dict_arg tests/test_cmd2.py::test_ver tests/test_cmd2.py::test_empty_statement tests/test_cmd2.py::test_base_help tests/test_cmd2.py::test_base_help_verbose tests/test_cmd2.py::test_base_help_history tests/test_cmd2.py::test_base_argparse_help tests/test_cmd2.py::test_base_shortcuts tests/test_cmd2.py::test_base_show tests/test_cmd2.py::test_base_show_long tests/test_cmd2.py::test_base_show_readonly tests/test_cmd2.py::test_base_set tests/test_cmd2.py::test_set_not_supported tests/test_cmd2.py::test_set_quiet tests/test_cmd2.py::test_base_shell tests/test_cmd2.py::test_base_py tests/test_cmd2.py::test_base_run_python_script tests/test_cmd2.py::test_base_run_pyscript tests/test_cmd2.py::test_recursive_pyscript_not_allowed tests/test_cmd2.py::test_pyscript_with_nonexist_file tests/test_cmd2.py::test_pyscript_with_exception tests/test_cmd2.py::test_pyscript_requires_an_argument tests/test_cmd2.py::test_base_error tests/test_cmd2.py::test_base_history tests/test_cmd2.py::test_history_script_format tests/test_cmd2.py::test_history_with_string_argument tests/test_cmd2.py::test_history_with_integer_argument tests/test_cmd2.py::test_history_with_integer_span tests/test_cmd2.py::test_history_with_span_start tests/test_cmd2.py::test_history_with_span_end tests/test_cmd2.py::test_history_with_span_index_error tests/test_cmd2.py::test_history_output_file tests/test_cmd2.py::test_history_edit tests/test_cmd2.py::test_history_run_all_commands tests/test_cmd2.py::test_history_run_one_command tests/test_cmd2.py::test_base_load tests/test_cmd2.py::test_load_with_empty_args tests/test_cmd2.py::test_load_with_nonexistent_file tests/test_cmd2.py::test_load_with_empty_file tests/test_cmd2.py::test_load_with_binary_file tests/test_cmd2.py::test_load_with_utf8_file tests/test_cmd2.py::test_load_nested_loads tests/test_cmd2.py::test_base_runcmds_plus_hooks tests/test_cmd2.py::test_base_relative_load tests/test_cmd2.py::test_relative_load_requires_an_argument tests/test_cmd2.py::test_feedback_to_output_true tests/test_cmd2.py::test_feedback_to_output_false tests/test_cmd2.py::test_allow_redirection tests/test_cmd2.py::test_input_redirection tests/test_cmd2.py::test_pipe_to_shell tests/test_cmd2.py::test_pipe_to_shell_error tests/test_cmd2.py::test_base_timing tests/test_cmd2.py::test_base_debug tests/test_cmd2.py::test_base_colorize tests/test_cmd2.py::test_edit_no_editor tests/test_cmd2.py::test_edit_file tests/test_cmd2.py::test_edit_file_with_spaces tests/test_cmd2.py::test_edit_blank tests/test_cmd2.py::test_base_py_interactive tests/test_cmd2.py::test_exclude_from_history tests/test_cmd2.py::test_base_cmdloop_with_queue tests/test_cmd2.py::test_base_cmdloop_without_queue tests/test_cmd2.py::test_cmdloop_without_rawinput tests/test_cmd2.py::test_precmd_hook_success tests/test_cmd2.py::test_precmd_hook_failure tests/test_cmd2.py::test_default_to_shell_unknown tests/test_cmd2.py::test_default_to_shell_good tests/test_cmd2.py::test_default_to_shell_failure tests/test_cmd2.py::test_ansi_prompt_not_esacped tests/test_cmd2.py::test_ansi_prompt_escaped tests/test_cmd2.py::test_custom_command_help tests/test_cmd2.py::test_custom_help_menu tests/test_cmd2.py::test_help_undocumented tests/test_cmd2.py::test_help_overridden_method tests/test_cmd2.py::test_help_cat_base tests/test_cmd2.py::test_help_cat_verbose tests/test_cmd2.py::test_select_options tests/test_cmd2.py::test_select_invalid_option tests/test_cmd2.py::test_select_list_of_strings tests/test_cmd2.py::test_select_list_of_tuples tests/test_cmd2.py::test_select_uneven_list_of_tuples tests/test_cmd2.py::test_help_with_no_docstring tests/test_cmd2.py::test_which_editor_bad tests/test_cmd2.py::test_multiline_complete_empty_statement_raises_exception tests/test_cmd2.py::test_multiline_complete_statement_without_terminator tests/test_cmd2.py::test_clipboard_failure tests/test_cmd2.py::test_cmdresult tests/test_cmd2.py::test_is_text_file_bad_input tests/test_cmd2.py::test_eof tests/test_cmd2.py::test_eos tests/test_cmd2.py::test_echo tests/test_cmd2.py::test_pseudo_raw_input_tty_rawinput_true tests/test_cmd2.py::test_pseudo_raw_input_tty_rawinput_false tests/test_cmd2.py::test_pseudo_raw_input_piped_rawinput_true_echo_true tests/test_cmd2.py::test_pseudo_raw_input_piped_rawinput_true_echo_false tests/test_cmd2.py::test_pseudo_raw_input_piped_rawinput_false_echo_true tests/test_cmd2.py::test_pseudo_raw_input_piped_rawinput_false_echo_false tests/test_cmd2.py::test_raw_input tests/test_cmd2.py::test_stdin_input tests/test_cmd2.py::test_empty_stdin_input tests/test_cmd2.py::test_poutput_string tests/test_cmd2.py::test_poutput_zero tests/test_cmd2.py::test_poutput_empty_string tests/test_cmd2.py::test_poutput_none tests/test_cmd2.py::test_alias tests/test_cmd2.py::test_alias_lookup_invalid_alias tests/test_cmd2.py::test_alias_with_invalid_name tests/test_cmd2.py::test_unalias tests/test_cmd2.py::test_unalias_all tests/test_cmd2.py::test_unalias_non_existing tests/test_completion.py::test_complete_command_single tests/test_completion.py::test_complete_empty_arg tests/test_completion.py::test_complete_bogus_command tests/test_completion.py::test_cmd2_command_completion_single tests/test_completion.py::test_cmd2_command_completion_multiple tests/test_completion.py::test_cmd2_command_completion_nomatch tests/test_completion.py::test_cmd2_help_completion_single tests/test_completion.py::test_cmd2_help_completion_multiple tests/test_completion.py::test_cmd2_help_completion_nomatch tests/test_completion.py::test_shell_command_completion_shortcut tests/test_completion.py::test_shell_command_completion_doesnt_match_wildcards tests/test_completion.py::test_shell_command_completion_multiple tests/test_completion.py::test_shell_command_completion_nomatch tests/test_completion.py::test_shell_command_completion_doesnt_complete_when_just_shell tests/test_completion.py::test_shell_command_completion_does_path_completion_when_after_command tests/test_completion.py::test_path_completion_single_end tests/test_completion.py::test_path_completion_multiple tests/test_completion.py::test_path_completion_nomatch tests/test_completion.py::test_default_to_shell_completion tests/test_completion.py::test_path_completion_cwd tests/test_completion.py::test_path_completion_doesnt_match_wildcards tests/test_completion.py::test_path_completion_expand_user_dir tests/test_completion.py::test_path_completion_user_expansion tests/test_completion.py::test_path_completion_directories_only tests/test_completion.py::test_basic_completion_single tests/test_completion.py::test_basic_completion_multiple tests/test_completion.py::test_basic_completion_nomatch tests/test_completion.py::test_delimiter_completion tests/test_completion.py::test_flag_based_completion_single tests/test_completion.py::test_flag_based_completion_multiple tests/test_completion.py::test_flag_based_completion_nomatch tests/test_completion.py::test_flag_based_default_completer tests/test_completion.py::test_flag_based_callable_completer tests/test_completion.py::test_index_based_completion_single tests/test_completion.py::test_index_based_completion_multiple tests/test_completion.py::test_index_based_completion_nomatch tests/test_completion.py::test_index_based_default_completer tests/test_completion.py::test_index_based_callable_completer tests/test_completion.py::test_tokens_for_completion_quoted tests/test_completion.py::test_tokens_for_completion_unclosed_quote tests/test_completion.py::test_tokens_for_completion_redirect tests/test_completion.py::test_tokens_for_completion_quoted_redirect tests/test_completion.py::test_tokens_for_completion_redirect_off tests/test_completion.py::test_parseline_command_and_args tests/test_completion.py::test_parseline_emptyline tests/test_completion.py::test_parseline_strips_line tests/test_completion.py::test_parseline_expands_alias tests/test_completion.py::test_parseline_expands_shortcuts tests/test_completion.py::test_add_opening_quote_basic_no_text tests/test_completion.py::test_add_opening_quote_basic_nothing_added tests/test_completion.py::test_add_opening_quote_basic_quote_added tests/test_completion.py::test_add_opening_quote_basic_text_is_common_prefix tests/test_completion.py::test_add_opening_quote_delimited_no_text tests/test_completion.py::test_add_opening_quote_delimited_nothing_added tests/test_completion.py::test_add_opening_quote_delimited_quote_added tests/test_completion.py::test_add_opening_quote_delimited_text_is_common_prefix tests/test_completion.py::test_add_opening_quote_delimited_space_in_prefix tests/test_completion.py::test_cmd2_subcommand_completion_single_end tests/test_completion.py::test_cmd2_subcommand_completion_multiple tests/test_completion.py::test_cmd2_subcommand_completion_nomatch tests/test_completion.py::test_cmd2_subcmd_with_unknown_completion_single_end tests/test_completion.py::test_cmd2_subcmd_with_unknown_completion_multiple tests/test_completion.py::test_cmd2_subcmd_with_unknown_completion_nomatch tests/test_completion.py::test_cmd2_help_subcommand_completion_single tests/test_completion.py::test_cmd2_help_subcommand_completion_multiple tests/test_completion.py::test_cmd2_help_subcommand_completion_nomatch tests/test_completion.py::test_subcommand_tab_completion_with_no_completer tests/test_completion.py::test_cmd2_submenu_completion_single_end tests/test_completion.py::test_cmd2_submenu_completion_multiple tests/test_completion.py::test_cmd2_submenu_completion_nomatch tests/test_completion.py::test_cmd2_submenu_completion_after_submenu_match tests/test_completion.py::test_cmd2_submenu_completion_after_submenu_nomatch tests/test_completion.py::test_cmd2_help_submenu_completion_multiple tests/test_completion.py::test_cmd2_help_submenu_completion_nomatch tests/test_completion.py::test_cmd2_help_submenu_completion_subcommands
cmd2/cmd2.py docs/argument_processing.rst examples/tab_autocompletion.py CHANGELOG.md examples/subcommands.py cmd2/argparse_completer.py
cmd2/cmd2.py docs/argument_processing.rst examples/tab_autocompletion.py CHANGELOG.md examples/subcommands.py cmd2/argparse_completer.py