@@ -165,7 +165,7 @@ def set_use_arg_list(val):
165165 USE_ARG_LIST = val
166166
167167
168- def flag_based_complete (text , line , begidx , endidx , flag_dict ):
168+ def flag_based_complete (text , line , begidx , endidx , flag_dict , default_completer = None ):
169169 """
170170 Tab completes based on a particular flag preceding the text being completed
171171 :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
@@ -178,6 +178,8 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict):
178178 values - there are two types of values
179179 1. iterable list of strings to match against (dictionaries, lists, etc.)
180180 2. function that performs tab completion (ex: path_complete)
181+ :param default_completer: callable - an optional completer to use if no flags in flag_dict precede the text
182+ being completed
181183 :return: List[str] - a list of possible tab completions
182184 """
183185 completions = []
@@ -191,9 +193,12 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict):
191193
192194 # Must have at least the command and one argument
193195 if len (tokens ) > 1 :
194- try :
195- # Get the argument that precedes the text being completed
196- flag = tokens [len (tokens ) - 1 ]
196+
197+ # Get the argument that precedes the text being completed
198+ flag = tokens [len (tokens ) - 1 ]
199+
200+ # Check if the flag is in the dictionary
201+ if flag in flag_dict :
197202
198203 # Check if this flag does completions using an Iterable
199204 if isinstance (flag_dict [flag ], collections .Iterable ):
@@ -209,13 +214,14 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict):
209214 completer_func = flag_dict [flag ]
210215 completions = completer_func (text , line , begidx , endidx )
211216
212- except KeyError :
213- pass
217+ # Otherwise check if there is a default completer
218+ elif default_completer is not None :
219+ completions = default_completer (text , line , begidx , endidx )
214220
215221 return completions
216222
217223
218- def index_based_complete (text , line , begidx , endidx , index_dict ):
224+ def index_based_complete (text , line , begidx , endidx , index_dict , default_completer = None ):
219225 """
220226 Tab completes based on a fixed position in the input string
221227 :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
@@ -228,6 +234,8 @@ def index_based_complete(text, line, begidx, endidx, index_dict):
228234 values - there are two types of values
229235 1. iterable list of strings to match against (dictionaries, lists, etc.)
230236 2. function that performs tab completion (ex: path_complete)
237+ :param default_completer: callable - an optional completer to use if the text being completed is not at
238+ any index in index_dict
231239 :return: List[str] - a list of possible tab completions
232240 """
233241 completions = []
@@ -241,9 +249,12 @@ def index_based_complete(text, line, begidx, endidx, index_dict):
241249
242250 # Must have at least the command
243251 if len (tokens ) > 0 :
244- try :
245- # Get the index of the text being completed
246- index = len (tokens )
252+
253+ # Get the index of the text being completed
254+ index = len (tokens )
255+
256+ # Check if the index is in the dictionary
257+ if index in index_dict :
247258
248259 # Check if this index does completions using an Iterable
249260 if isinstance (index_dict [index ], collections .Iterable ):
@@ -259,8 +270,9 @@ def index_based_complete(text, line, begidx, endidx, index_dict):
259270 completer_func = index_dict [index ]
260271 completions = completer_func (text , line , begidx , endidx )
261272
262- except KeyError :
263- pass
273+ # Otherwise check if there is a default completer
274+ elif default_completer is not None :
275+ completions = default_completer (text , line , begidx , endidx )
264276
265277 return completions
266278
@@ -1206,33 +1218,24 @@ def completenames(self, text, line, begidx, endidx):
12061218
12071219 return cmd_completion
12081220
1209- # noinspection PyUnusedLocal
1210- def complete_subcommand (self , text , line , begidx , endidx ):
1211- """Readline tab-completion method for completing argparse sub-command names."""
1212- command , args , foo = self .parseline (line )
1213- arglist = args .split ()
1214-
1215- if len (arglist ) <= 1 and command + ' ' + args == line :
1216- funcname = self ._func_named (command )
1217- if funcname :
1218- # Check to see if this function was decorated with an argparse ArgumentParser
1219- func = getattr (self , funcname )
1220- subcommand_names = func .__dict__ .get ('subcommand_names' , None )
1221+ def get_subcommands (self , command ):
1222+ """
1223+ Returns a list of a command's subcommands if they exist
1224+ :param command:
1225+ :return: A subcommand list or None
1226+ """
12211227
1222- # If this command has subcommands
1223- if subcommand_names is not None :
1224- arg = ''
1225- if arglist :
1226- arg = arglist [0 ]
1228+ subcommand_names = None
12271229
1228- matches = [sc for sc in subcommand_names if sc .startswith (arg )]
1230+ # Check if is a valid command
1231+ funcname = self ._func_named (command )
12291232
1230- # If completing the sub-command name and get exactly 1 result and are at end of line, add a space
1231- if len ( matches ) == 1 and endidx == len ( line ):
1232- matches [ 0 ] += ' '
1233- return matches
1233+ if funcname :
1234+ # Check to see if this function was decorated with an argparse ArgumentParser
1235+ func = getattr ( self , funcname )
1236+ subcommand_names = func . __dict__ . get ( 'subcommand_names' , None )
12341237
1235- return []
1238+ return subcommand_names
12361239
12371240 def complete (self , text , state ):
12381241 """Override of command method which returns the next possible completion for 'text'.
@@ -1280,27 +1283,21 @@ def complete(self, text, state):
12801283 if command == '' :
12811284 compfunc = self .completedefault
12821285 else :
1283- arglist = args .split ()
1284-
1285- compfunc = None
1286- # If the user has entered no more than a single argument after the command name
1287- if len (arglist ) <= 1 and command + ' ' + args == line :
1288- funcname = self ._func_named (command )
1289- if funcname :
1290- # Check to see if this function was decorated with an argparse ArgumentParser
1291- func = getattr (self , funcname )
1292- subcommand_names = func .__dict__ .get ('subcommand_names' , None )
1293-
1294- # If this command has subcommands
1295- if subcommand_names is not None :
1296- compfunc = self .complete_subcommand
1297-
1298- if compfunc is None :
1299- # This command either doesn't have sub-commands or the user is past the point of entering one
1300- try :
1301- compfunc = getattr (self , 'complete_' + command )
1302- except AttributeError :
1303- compfunc = self .completedefault
1286+
1287+ # Get the completion function for this command
1288+ try :
1289+ compfunc = getattr (self , 'complete_' + command )
1290+ except AttributeError :
1291+ compfunc = self .completedefault
1292+
1293+ # If there are subcommands, then try completing those if the cursor is in
1294+ # the correct position, otherwise default to using compfunc
1295+ subcommands = self .get_subcommands (command )
1296+ if subcommands is not None :
1297+ index_dict = {1 : subcommands }
1298+ compfunc = functools .partial (index_based_complete ,
1299+ index_dict = index_dict ,
1300+ default_completer = compfunc )
13041301
13051302 # Call the completer function
13061303 self .completion_matches = compfunc (text , line , begidx , endidx )
@@ -2055,11 +2052,11 @@ def complete_shell(self, text, line, begidx, endidx):
20552052 try :
20562053 tokens = shlex .split (line [:endidx ], posix = POSIX_SHLEX )
20572054 except ValueError :
2058- # Invalid syntax for shlex (Probably due to missing closing quotes )
2055+ # Invalid syntax for shlex (Probably due to missing closing quote )
20592056 return []
20602057
20612058 if len (tokens ) == 1 :
2062- # Don't tab complete anything if user only typ
2059+ # Don't tab complete anything if user only typed shell
20632060 return []
20642061
20652062 # Check if we are still completing the shell command
0 commit comments