1+ # PYTHON_ARGCOMPLETE_OK
12"""The root `jupyter` command.
23
34This does nothing other than dispatch to subcommands or output path info.
@@ -37,6 +38,15 @@ def epilog(self, x):
3738 """Ignore epilog set in Parser.__init__"""
3839 pass
3940
41+ def argcomplete (self ):
42+ """Trigger auto-completion, if enabled"""
43+ try :
44+ import argcomplete # type: ignore[import]
45+
46+ argcomplete .autocomplete (self )
47+ except ImportError :
48+ pass
49+
4050
4151def jupyter_parser () -> JupyterParser :
4252 """Create a jupyter parser object."""
@@ -48,7 +58,11 @@ def jupyter_parser() -> JupyterParser:
4858 group .add_argument (
4959 "--version" , action = "store_true" , help = "show the versions of core jupyter packages and exit"
5060 )
51- group .add_argument ("subcommand" , type = str , nargs = "?" , help = "the subcommand to launch" )
61+ subcommand_action = group .add_argument (
62+ "subcommand" , type = str , nargs = "?" , help = "the subcommand to launch"
63+ )
64+ # For argcomplete, supply all known subcommands
65+ subcommand_action .completer = lambda * args , ** kwargs : list_subcommands () # type: ignore[attr-defined]
5266
5367 group .add_argument ("--config-dir" , action = "store_true" , help = "show Jupyter config dir" )
5468 group .add_argument ("--data-dir" , action = "store_true" , help = "show Jupyter data dir" )
@@ -173,13 +187,49 @@ def _path_with_self():
173187 return path_list
174188
175189
190+ def _evaluate_argcomplete (parser : JupyterParser ) -> List [str ]:
191+ """If argcomplete is enabled, trigger autocomplete or return current words
192+
193+ If the first word looks like a subcommand, return the current command
194+ that is attempting to be completed so that the subcommand can evaluate it;
195+ otherwise auto-complete using the main parser.
196+ """
197+ try :
198+ # traitlets >= 5.8 provides some argcomplete support,
199+ # use helper methods to jump to argcomplete
200+ from traitlets .config .argcomplete_config import (
201+ get_argcomplete_cwords ,
202+ increment_argcomplete_index ,
203+ )
204+
205+ cwords = get_argcomplete_cwords ()
206+ if cwords and len (cwords ) > 1 and not cwords [1 ].startswith ("-" ):
207+ # If first completion word looks like a subcommand,
208+ # increment word from which to start handling arguments
209+ increment_argcomplete_index ()
210+ return cwords
211+ else :
212+ # Otherwise no subcommand, directly autocomplete and exit
213+ parser .argcomplete ()
214+ except ImportError :
215+ # traitlets >= 5.8 not available, just try to complete this without
216+ # worrying about subcommands
217+ parser .argcomplete ()
218+ raise AssertionError ("Control flow should not reach end of autocomplete()" )
219+
220+
176221def main () -> None :
177222 """The command entry point."""
178223 parser = jupyter_parser ()
179- if len (sys .argv ) > 1 and not sys .argv [1 ].startswith ("-" ):
224+ argv = sys .argv
225+ subcommand = None
226+ if "_ARGCOMPLETE" in os .environ :
227+ argv = _evaluate_argcomplete (parser )
228+ subcommand = argv [1 ]
229+ elif len (argv ) > 1 and not argv [1 ].startswith ("-" ):
180230 # Don't parse if a subcommand is given
181231 # Avoids argparse gobbling up args passed to subcommand, such as `-h`.
182- subcommand = sys . argv [1 ]
232+ subcommand = argv [1 ]
183233 else :
184234 args , opts = parser .parse_known_args ()
185235 subcommand = args .subcommand
@@ -343,7 +393,7 @@ def main() -> None:
343393 sys .exit (str (e ))
344394
345395 try :
346- _execvp (command , [command ] + sys . argv [2 :])
396+ _execvp (command , [command ] + argv [2 :])
347397 except OSError as e :
348398 sys .exit (f"Error executing Jupyter command { subcommand !r} : { e } " )
349399
0 commit comments