Repository: python-cmd2/cmd2
Pull request #1141 (2021-09-02 18:46:47)

Argparse tab completer will complete remaining flag names if there are no more positionals to complete

codecov[bot]: # [Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/1141?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2) Report
> Merging [#1141](https://codecov.io/gh/python-cmd2/cmd2/pull/1141?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2) (9e5d396) into [master](https://codecov.io/gh/python-cmd2/cmd2/commit/ceacf225e5e267a96bf5db4fc5bb7702fa6f8a72?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2) (ceacf22) will **not change** coverage.
> The diff coverage is `100.00%`.

> :exclamation: Current head 9e5d396 differs from pull request most recent head c3f44b4. Consider uploading reports for the commit c3f44b4 to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/python-cmd2/cmd2/pull/1141/graphs/tree.svg?width=650&height=150&src=pr&token=eks2PUwa6A&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2)](https://codecov.io/gh/python-cmd2/cmd2/pull/1141?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2)

```diff
@@           Coverage Diff           @@
##           master    #1141   +/-   ##
=======================================
  Coverage   98.35%   98.35%           
=======================================
  Files          22       22           
  Lines        4987     4987           
=======================================
  Hits         4905     4905           
  Misses         82       82           
```


| [Impacted Files](https://codecov.io/gh/python-cmd2/cmd2/pull/1141?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2) | Coverage Δ | |
|---|---|---|
| [cmd2/argparse\_completer.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1141/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2#diff-Y21kMi9hcmdwYXJzZV9jb21wbGV0ZXIucHk=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/1141?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/1141?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2). Last update [ceacf22...c3f44b4](https://codecov.io/gh/python-cmd2/cmd2/pull/1141?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-cmd2).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0a1daaf..dfd6ee05 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@
   * Tab completion of `CompletionItems` now includes divider row comprised of `Cmd.ruler` character.
   * Removed `--verbose` flag from set command since descriptions always show now.
   * All cmd2 built-in commands now populate `self.last_result`.
+  * Argparse tab completer will complete remaining flag names if there are no more positionals to complete.
 * Deletions (potentially breaking changes)
   * Deleted ``set_choices_provider()`` and ``set_completer()`` which were deprecated in 2.1.2
 
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index f614ac32..26835513 100644
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -504,9 +504,10 @@ class ArgparseCompleter:
             elif not _single_prefix_char(text, self._parser) or skip_remaining_flags:
                 raise _NoResultsError(self._parser, pos_arg_state.action)
 
-        # Handle case in which text is a single flag prefix character that
-        # didn't complete against any argument values.
-        if _single_prefix_char(text, self._parser) and not skip_remaining_flags:
+        # If we aren't skipping remaining flags, then complete flag names if either is True:
+        #   1. text is a single flag prefix character that didn't complete against any argument values
+        #   2. there are no more positionals to complete
+        if not skip_remaining_flags and (_single_prefix_char(text, self._parser) or not remaining_positionals):
             return self._complete_flags(text, line, begidx, endidx, matched_flags)
 
         return completion_results
diff --git a/examples/help_categories.py b/examples/help_categories.py
index c5c9af86..b2a1623f 100755
--- a/examples/help_categories.py
+++ b/examples/help_categories.py
@@ -10,6 +10,7 @@ import functools
 import cmd2
 from cmd2 import (
     COMMAND_NAME,
+    argparse_custom,
 )
 
 
@@ -55,7 +56,9 @@ class HelpCategories(cmd2.Cmd):
         """Deploy command"""
         self.poutput('Deploy')
 
-    start_parser = cmd2.DEFAULT_ARGUMENT_PARSER(description='Start', epilog='my_decorator runs even with argparse errors')
+    start_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(
+        description='Start', epilog='my_decorator runs even with argparse errors'
+    )
     start_parser.add_argument('when', choices=START_TIMES, help='Specify when to start')
 
     @my_decorator
@@ -72,7 +75,7 @@ class HelpCategories(cmd2.Cmd):
         """Redeploy command"""
         self.poutput('Redeploy')
 
-    restart_parser = cmd2.DEFAULT_ARGUMENT_PARSER(
+    restart_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(
         description='Restart', epilog='my_decorator does not run when argparse errors'
     )
     restart_parser.add_argument('when', choices=START_TIMES, help='Specify when to restart')


Test patch:
diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py
index 6cd9a7fe..1d585122 100644
--- a/tests/test_argparse_completer.py
+++ b/tests/test_argparse_completer.py
@@ -98,6 +98,15 @@ class ArgparseCompleterTester(cmd2.Cmd):
     def do_plus_flag(self, args: argparse.Namespace) -> None:
         pass
 
+    # A parser with a positional and flags. Used to test that remaining flag names are completed when all positionals are done.
+    pos_and_flag_parser = Cmd2ArgumentParser()
+    pos_and_flag_parser.add_argument("positional", choices=["a", "choice"])
+    pos_and_flag_parser.add_argument("-f", "--flag", action='store_true')
+
+    @with_argparser(pos_and_flag_parser)
+    def do_pos_and_flag(self, args: argparse.Namespace) -> None:
+        pass
+
     ############################################################################################################
     # Begin code related to testing choices and choices_provider parameters
     ############################################################################################################
@@ -519,6 +528,11 @@ def test_subcommand_completions(ac_app, subcommand, text, completions):
         ('flag --help --', '--', [], []),
         ('plus_flag --', '++', [], []),
         ('plus_flag ++help --', '++', [], []),
+        # Test remaining flag names complete after all positionals are complete
+        ('pos_and_flag', '', ['a', 'choice'], ['a', 'choice']),
+        ('pos_and_flag choice ', '', ['--flag', '--help', '-f', '-h'], ['[-f, --flag]', '[-h, --help]']),
+        ('pos_and_flag choice -f ', '', ['--help', '-h'], ['[-h, --help]']),
+        ('pos_and_flag choice -f -h ', '', [], []),
     ],
 )
 def test_autcomp_flag_completion(ac_app, command_and_args, text, completion_matches, display_matches):