Skip to content

Commit

Permalink
Help string support for fish
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey A Volkov committed Mar 3, 2020
1 parent abfa187 commit ed1f824
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 21 deletions.
10 changes: 10 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,16 @@ or create new completion file, e.g::

register-python-argcomplete --shell fish ~/.config/fish/completions/my-awesome-script.fish

Completion Description For Fish
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default help string is added as completion description.

.. image:: docs/fish_help_string.png

You can disable this feature by removing ``_ARGCOMPLETE_DFS`` variable, e.g::

register-python-argcomplete --shell fish my-awesome-script | grep -v _ARGCOMPLETE_DFS | .

Python Support
--------------
Argcomplete requires Python 2.7 or 3.5+.
Expand Down
37 changes: 20 additions & 17 deletions argcomplete/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ def __call__(self, argument_parser, always_complete_options=True, exit_method=os
debug("Invalid value for IFS, quitting [{v}]".format(v=ifs))
exit_method(1)

dfs = os.environ.get("_ARGCOMPLETE_DFS")
if dfs and len(dfs) != 1:
debug("Invalid value for DFS, quitting [{v}]".format(v=dfs))
exit_method(1)

comp_line = os.environ["COMP_LINE"]
comp_point = int(os.environ["COMP_POINT"])

Expand Down Expand Up @@ -226,6 +231,12 @@ def __call__(self, argument_parser, always_complete_options=True, exit_method=os

completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos)

if dfs:
display_completions = {key_part: value.replace(ifs, " ") if value else ""
for key, value in self._display_completions.items()
for key_part in key}
completions = [dfs.join((key, display_completions.get(key) or "")) for key in completions]

debug("\nReturning completions:", completions)
output_stream.write(ifs.join(completions).encode(sys_encoding))
output_stream.flush()
Expand Down Expand Up @@ -321,18 +332,10 @@ def __call__(self, parser, namespace, values, option_string=None):
return self.active_parsers

def _get_subparser_completions(self, parser, cword_prefix):
def filter_aliases(metavar, dest, prefix):
if not metavar:
return dest if dest and dest.startswith(prefix) else ""

# metavar combines dest and aliases with ",".
a = metavar.replace(",", "").split()
return " ".join(x for x in a if x.startswith(prefix))

for action in parser._get_subactions():
subcmd_with_aliases = filter_aliases(action.metavar, action.dest, cword_prefix)
if subcmd_with_aliases:
self._display_completions[subcmd_with_aliases] = action.help
dest = action.dest
if dest and dest.startswith(cword_prefix):
self._display_completions[dest] = action.help

completions = [subcmd for subcmd in parser.choices.keys() if subcmd.startswith(cword_prefix)]
return completions
Expand All @@ -350,8 +353,8 @@ def _include_options(self, action, cword_prefix):

def _get_option_completions(self, parser, cword_prefix):
self._display_completions.update(
[[" ".join(ensure_str(x) for x in action.option_strings
if ensure_str(x).startswith(cword_prefix)), action.help]
[[tuple(ensure_str(x) for x in action.option_strings
if ensure_str(x).startswith(cword_prefix)), action.help]
for action in parser._actions
if action.option_strings])

Expand Down Expand Up @@ -437,18 +440,18 @@ def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_
completions += completions_from_callable
if isinstance(completer, completers.ChoicesCompleter):
self._display_completions.update(
[[x, active_action.help] for x in completions_from_callable])
[[(x,), active_action.help] for x in completions_from_callable])
else:
self._display_completions.update(
[[x, ""] for x in completions_from_callable])
[[(x,), ""] for x in completions_from_callable])
else:
debug("Completer is not callable, trying the readline completer protocol instead")
for i in range(9999):
next_completion = completer.complete(cword_prefix, i)
if next_completion is None:
break
if self.validator(next_completion, cword_prefix):
self._display_completions.update({next_completion: ""})
self._display_completions.update({(next_completion,): ""})
completions.append(next_completion)
if optional_prefix:
completions = [optional_prefix + "=" + completion for completion in completions]
Expand Down Expand Up @@ -635,7 +638,7 @@ def display_completions(substitution, matches, longest_match_length):
readline.set_completion_display_matches_hook(display_completions)
"""
return self._display_completions
return {" ".join(k): v for k, v in self._display_completions.items()}

class ExclusiveCompletionFinder(CompletionFinder):
@staticmethod
Expand Down
1 change: 1 addition & 0 deletions argcomplete/shell_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
fishcode = r'''
function __fish_%(executable)s_complete
set -x _ARGCOMPLETE 1
set -x _ARGCOMPLETE_DFS \t
set -x _ARGCOMPLETE_IFS \n
set -x _ARGCOMPLETE_SUPPRESS_SPACE 1
set -x _ARGCOMPLETE_SHELL fish
Expand Down
Binary file added docs/fish_help_string.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions test/prog
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ def get_comp_point(*args, **kwargs):

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
subparsers.add_parser('basic').add_argument('arg', choices=['foo', 'bar', 'baz'])
subparsers.add_parser('basic', help='basic help\nnext line of help').add_argument('arg', choices=['foo', 'bar', 'baz'])
subparsers.add_parser('space').add_argument('arg', choices=['foo bar', 'baz'])
subparsers.add_parser('cont').add_argument('arg').completer = complete_cont
subparsers.add_parser('spec').add_argument('arg', choices=['d$e$f', 'd$e$g', 'x!x', r'y\y'])
subparsers.add_parser('quote').add_argument('arg', choices=["1'1", '2"2'])
subparsers.add_parser('break').add_argument('arg', choices=['a:b:c', 'a:b:d'])
subparsers.add_parser('break', help="break help").add_argument('arg', choices=['a:b:c', 'a:b:d'])
subparsers.add_parser('env').add_argument('arg').completer = check_environ
subparsers.add_parser('debug').add_argument('arg').completer = print_output
subparsers.add_parser('point', add_help=False).add_argument('arg', nargs='*').completer = get_comp_point
Expand Down
31 changes: 29 additions & 2 deletions test/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ def run_completer(self, parser, command, point=None, completer=autocomplete, **k
with TemporaryFile() as t:
os.environ["COMP_LINE"] = ensure_bytes(command) if USING_PYTHON2 else command
os.environ["COMP_POINT"] = point
self.assertRaises(SystemExit, completer, parser, output_stream=t,
exit_method=sys.exit, **kwargs)
with self.assertRaises(SystemExit) as cm:
completer(parser, output_stream=t, exit_method=sys.exit, **kwargs)
if cm.exception.code != 0:
raise Exception("Unexpected exit code %d" % cm.exception.code)
t.seek(0)
return t.read().decode(sys_encoding).split(IFS)

Expand Down Expand Up @@ -757,6 +759,31 @@ def test_shellcode_utility(self):
sc = shellcode(["prog"], use_defaults=False, shell="woosh", complete_arguments=["-o", "nospace"])
sc = shellcode(["prog"], shell="fish")

def test_option_help(self):
os.environ["_ARGCOMPLETE_DFS"] = "\t"
os.environ["_ARGCOMPLETE_SUPPRESS_SPACE"] = "1"
os.environ["_ARGCOMPLETE_SHELL"] = "fish"

p = ArgumentParser()
p.add_argument("--foo", help="foo" + IFS + "help")
p.add_argument("--bar", "--bar2", help="bar help")

subparsers = p.add_subparsers()
subparsers.add_parser("subcommand", help="subcommand help")
subparsers.add_parser("subcommand 2", help="subcommand 2 help")

completions = self.run_completer(p, "prog --f")
self.assertEqual(set(completions), {"--foo\tfoo help"})

completions = self.run_completer(p, "prog --b")
self.assertEqual(set(completions), {"--bar\tbar help", "--bar2\tbar help"})

completions = self.run_completer(p, "prog sub")
self.assertEqual(set(completions), {"subcommand\tsubcommand help", "subcommand 2\tsubcommand 2 help"})

os.environ["_ARGCOMPLETE_DFS"] = "invalid"
self.assertRaises(Exception, self.run_completer, p, "prog --b")

class TestArgcompleteREPL(unittest.TestCase):
def setUp(self):
pass
Expand Down

0 comments on commit ed1f824

Please sign in to comment.