Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Authors
* Oliver Beckstein (`webpage-ob <https://becksteinlab.physics.asu.edu>`_, `github-ob <https://github.com/orbeckst>`_)
* Lily Wang (`github-lw <https://github.com/lilyminium>`_)
* Henrik Jäger (`github-hj <https://github.com/hejamu>`_)
* Shreejan Dolai (`github-sd <https://github.com/spyke7>`_)
6 changes: 6 additions & 0 deletions docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Changelog
=========

Unreleased
----------

* Added argcomplete for tab completion
* Updated docs

v0.1.33 (2025-10-20)
------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ sphinx-argparse
CommonMark
mock
mdanalysis_sphinx_theme >=1.0.1
argcomplete
1 change: 1 addition & 0 deletions docs/src/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
installation
philosophy
usage
tab-completion
contributing
api
changelog
Expand Down
119 changes: 119 additions & 0 deletions docs/src/tab-completion.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
==============
Tab-Completion
==============

``mdacli`` includes built-in support for command-line tab-completion

Activation
==========

The activation method depends on your shell:

Bash
----

**Temporary (current session only)**::

eval "$(register-python-argcomplete mda)"

**Permanent (recommended)**

Add to your ``~/.bashrc``::

echo 'eval "$(register-python-argcomplete mda)"' >> ~/.bashrc
source ~/.bashrc

Zsh
---

Add to your ``~/.zshrc``::

autoload -U bashcompinit
bashcompinit
eval "$(register-python-argcomplete mda)"

Then reload::

source ~/.zshrc

Fish
----

Generate the completion file::

register-python-argcomplete --shell fish mda > ~/.config/fish/completions/mda.fish

Restart your Fish shell or run::

source ~/.config/fish/config.fish

Tcsh
----

Add to your shell startup file::

eval `register-python-argcomplete --shell tcsh mda`

Usage Examples
==============

Once enabled, tab-completion works for:

**Module names**::

mda <TAB>
# Shows: AlignTraj, AverageStructure, Contacts, DensityAnalysis, ...

**Partial module names**::

mda RM<TAB>
# Shows: RMSD, RMSF

**Options and flags**::

mda RMSD -<TAB>
# Shows: -s, -f, -atomgroup, -b, -e, -dt, -v, --debug, --version, ...

**Case insensitive**::

mda rmsd<TAB> # Also works
mda RmSd<TAB> # Also works

Troubleshooting
===============

Tab-completion not working
--------------------------

1. **Verify argcomplete is installed**::

python -c "import argcomplete; print(argcomplete.__version__)"

2. **Check if activation command was added**::

grep "register-python-argcomplete mda" ~/.bashrc

3. **Reload your shell**::

source ~/.bashrc # or restart terminal

4. **Test basic completion**::

mda <TAB>

Still not working
-----------------

- Make sure you've restarted your terminal or sourced the configuration file
- For Zsh, ensure ``bashcompinit`` is loaded before argcomplete
- Check that ``mda`` is in your PATH: ``which mda``
- Try running the registration command manually in your current shell

Global activation (for all Python scripts)
-------------------------------------------

To enable argcomplete for all Python scripts at once::

activate-global-python-argcomplete

This requires root/admin privileges and will enable completion system-wide.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ classifiers = [
dependencies = [
"MDAnalysis>=2.10.0",
"threadpoolctl",
"argcomplete",
]

[project.urls]
Expand Down
6 changes: 5 additions & 1 deletion src/mdacli/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
#
# Copyright (c) 2021 Authors and contributors
#
Expand All @@ -19,6 +20,7 @@
https://docs.mdanalysis.org/stable/documentation_pages/analysis_modules.html
"""

import argcomplete
from MDAnalysis.analysis import __all__

import mdacli
Expand All @@ -37,7 +39,7 @@ def main():
"InterRDF_s",
]

mdacli.cli(
parser = mdacli.cli(
name="MDAnalysis",
module_list=[f"MDAnalysis.analysis.{m}" for m in __all__],
version=mdacli.__version__,
Expand All @@ -46,6 +48,8 @@ def main():
ignore_warnings=True,
)

argcomplete.autocomplete(parser)


if __name__ == "__main__":
main()
6 changes: 3 additions & 3 deletions src/mdacli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import traceback
import warnings

import argcomplete
from MDAnalysis.analysis.base import AnalysisBase
from threadpoolctl import threadpool_limits

Expand Down Expand Up @@ -89,15 +90,14 @@ def cli(

ap = init_base_argparse(name=name, version=version, description=description)

if len(sys.argv) < 2:
ap.error("A subcommand is required.")

# There is to much useless code execution done here:
# 1. We do not have to setup all possible clients all the time.
# i.e. for `mda RMSD` only the RMSD client should be build.
# 2. for something like `mdacli -h` We do not have to build every
# sub parser in complete detail.

setup_clients(ap, title=f"{name} Analysis Modules", members=modules)
argcomplete.autocomplete(ap)

args = ap.parse_args()

Expand Down
78 changes: 78 additions & 0 deletions tests/test_cli.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the tests you added didn't really fail even when before the completion was not working we should maybe think about more robust test cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous one was checking for setup
Setup was perfect before and after also
PYTHON_ARGCOMPLETE_OK was the main thing
It was not related with test cases, but crucial for bash completion

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import subprocess
import sys
from contextlib import suppress
from pathlib import Path

import pytest
Expand Down Expand Up @@ -47,6 +48,83 @@ def test_case_insensitive_with_flags(args):
subprocess.check_call(["mda", "--debug", args, "-h"])


def test_subparser_setup_for_tab_completion():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice test. But I think we should also have a test that the argcompletion is really setup. Maybe check the argcomplete repo how they do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

"""Test that subparsers are correctly set up for tab-completion.

This verifies that RMSF and RMSD modules are registered as subcommands,
which is what argcomplete needs for tab-completion to work.
"""
from MDAnalysis.analysis.base import AnalysisBase

from src.mdacli.libcli import find_cls_members, init_base_argparse, setup_clients

modules = find_cls_members(AnalysisBase, ["MDAnalysis.analysis.rms"])

parser = init_base_argparse(
name="MDAnalysis", version="0.1.0", description="Test CLI"
)

setup_clients(parser, title="MDAnalysis Analysis Modules", members=modules)

subparser_action = [
a for a in parser._subparsers._group_actions if hasattr(a, "choices")
][0]

choices = list(subparser_action.choices.keys())
assert "RMSF" in choices
assert "RMSD" in choices


def test_argcomplete_working():
"""Test that argcomplete is properly registered and working."""
import argparse
from unittest.mock import patch

from MDAnalysis.analysis import __all__

import src.mdacli
from src.mdacli.cli import cli

skip_mods = [
"AnalysisFromFunction",
"HydrogenBondAnalysis",
"WaterBridgeAnalysis",
"Contacts",
"PersistenceLength",
"InterRDF_s",
]

with (
patch("src.mdacli.cli.argcomplete.autocomplete") as mock_autocomplete,
patch("sys.argv", ["mda", "--help"]),
suppress(SystemExit),
):
cli(
name="MDAnalysis",
module_list=[f"MDAnalysis.analysis.{m}" for m in __all__],
version=src.mdacli.__version__,
description="Test",
skip_modules=skip_mods,
ignore_warnings=True,
)

# Verify that argcomplete.autocomplete was called
assert mock_autocomplete.called, "argcomplete.autocomplete() was not called"

# Verify it was called with an ArgumentParser instance
call_args = mock_autocomplete.call_args
assert call_args is not None, (
"argcomplete.autocomplete() was called with no arguments"
)

parser_arg = call_args[0][0]
msg = (
"argcomplete.autocomplete() should be called with ArgumentParser, "
f"got {type(parser_arg)}"
)
assert isinstance(parser_arg, argparse.ArgumentParser), msg


def test_running_analysis(tmpdir):
"""Test running a complete analysis."""
with tmpdir.as_cwd():
Expand Down
3 changes: 1 addition & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ setenv =
PYTHONUNBUFFERED=yes
usedevelop = true
deps =
MDAnalysis>=2.1.0
MDAnalysisTests>=2.1.0
MDAnalysisTests>=2.10.0
coverage[toml]
pytest
pytest-cov
Expand Down