diff --git a/docs/usage.rst b/docs/usage.rst index 042f6624..dc200345 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -313,3 +313,40 @@ This will create a ``noxfile.py`` based on the environments in your ``tox.ini``. .. _Generative environments: http://tox.readthedocs.io/en/latest/config.html#generating-environments-conditional-settings .. _substitutions: http://tox.readthedocs.io/en/latest/config.html#substitutions + + +Shell Completion +---------------- +Add the appropriate command to your shell's config file +so that it is run on startup. You will likely have to restart +or re-login for the autocompletion to start working. + +bash + +.. code-block:: console + + eval "$(register-python-argcomplete nox)" + +zsh + +.. code-block:: console + + # To activate completions for zsh you need to have + # bashcompinit enabled in zsh: + autoload -U bashcompinit + bashcompinit + + # Afterwards you can enable completion for nox: + eval "$(register-python-argcomplete nox)" + +tcsh + +.. code-block:: console + + eval `register-python-argcomplete --shell tcsh nox` + +fish + +.. code-block:: console + + register-python-argcomplete --shell fish nox | . \ No newline at end of file diff --git a/nox/_option_set.py b/nox/_option_set.py index 2b4830bc..a5492afc 100644 --- a/nox/_option_set.py +++ b/nox/_option_set.py @@ -21,6 +21,7 @@ import collections import functools +import argcomplete # type: ignore Namespace = argparse.Namespace ArgumentError = argparse.ArgumentError @@ -68,6 +69,7 @@ def __init__( finalizer_func=None, default=None, hidden=False, + completer=None, **kwargs ): self.name = name @@ -78,6 +80,7 @@ def __init__( self.merge_func = merge_func self.finalizer_func = finalizer_func self.hidden = hidden + self.completer = completer self.kwargs = kwargs self._default = default @@ -187,9 +190,11 @@ def add_group(self, name, *args, **kwargs): self.groups[name] = (args, kwargs) def _add_to_parser(self, parser, option): - parser.add_argument( + argument = parser.add_argument( *option.flags, help=option.help, default=option.default, **option.kwargs ) + if option.completer: + argument.completer = option.completer def parser(self): """Returns an ``ArgumentParser`` for this option set. @@ -233,6 +238,7 @@ def _finalize_args(self, args): def parse_args(self): parser = self.parser() + argcomplete.autocomplete(parser) args = parser.parse_args() try: diff --git a/nox/_options.py b/nox/_options.py index d421d902..c2da68c5 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -18,6 +18,7 @@ import sys from nox import _option_set +from nox.tasks import discover_manifest, filter_manifest, load_nox_module """All of nox's configuration options.""" @@ -97,6 +98,16 @@ def _color_finalizer(value, args): return sys.stdin.isatty() +def _session_completer(prefix, parsed_args, **kwargs): + global_config = parsed_args + module = load_nox_module(global_config) + manifest = discover_manifest(module, global_config) + filtered_manifest = filter_manifest(manifest, global_config) + return [ + session.friendly_name for session, _ in filtered_manifest.list_all_sessions() + ] + + def _posargs_finalizer(value, unused_args): """Removes any leading "--"s in the posargs array.""" if value and value[0] == "--": @@ -141,6 +152,7 @@ def _posargs_finalizer(value, unused_args): nargs="*", default=_sessions_default, help="Which sessions to run. By default, all sessions will run.", + completer=_session_completer, ), _option_set.Option( "keywords", diff --git a/setup.py b/setup.py index 0224dc1c..8d829e68 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,7 @@ packages=["nox"], include_package_data=True, install_requires=[ + "argcomplete>=1.9.4, <2.0", "colorlog>=2.6.1,<4.0.0", "py>=1.4.0,<2.0.0", "virtualenv>=14.0.0", diff --git a/tests/test__option_set.py b/tests/test__option_set.py index 323467b5..a4b64a4b 100644 --- a/tests/test__option_set.py +++ b/tests/test__option_set.py @@ -12,9 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys +from unittest import mock + import pytest from nox import _option_set +from nox import _options # The vast majority of _option_set is tested by test_main, but the test helper @@ -45,3 +49,14 @@ def test_namespace_non_existant_options_with_values(self): with pytest.raises(KeyError): optionset.namespace(non_existant_option="meep") + + def test_session_completer(self): + with mock.patch("sys.argv", [sys.executable]): + parsed_args = _options.options.parse_args() + all_nox_sessions = _options._session_completer( + prefix=None, parsed_args=parsed_args + ) + # if noxfile.py changes, this will have to change as well since these are + # some of the actual sessions found in noxfile.py + some_expected_sessions = ["cover", "blacken", "lint", "docs"] + assert len(set(some_expected_sessions) - set(all_nox_sessions)) == 0