@@ -182,11 +182,11 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict):
182182 """
183183 completions = []
184184
185- # Get all tokens prior to the text being completed
185+ # Get all tokens prior to text being completed
186186 try :
187187 tokens = shlex .split (line [:begidx ], posix = POSIX_SHLEX )
188188 except ValueError :
189- # Invalid syntax for shlex (Probably due to missing closing quotes )
189+ # Invalid syntax for shlex (Probably due to missing closing quote )
190190 return completions
191191
192192 # Must have at least the command and one argument
@@ -197,7 +197,7 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict):
197197
198198 # Check if this flag does completions using an Iterable
199199 if isinstance (flag_dict [flag ], collections .Iterable ):
200- strs_to_match = ( flag_dict [flag ])
200+ strs_to_match = flag_dict [flag ]
201201 completions = [cur_str for cur_str in strs_to_match if cur_str .startswith (text )]
202202
203203 # If there is only 1 match and it's at the end of the line, then add a space
@@ -232,11 +232,11 @@ def index_based_complete(text, line, begidx, endidx, index_dict):
232232 """
233233 completions = []
234234
235- # Get all tokens prior to the text being completed
235+ # Get all tokens prior to text being completed
236236 try :
237237 tokens = shlex .split (line [:begidx ], posix = POSIX_SHLEX )
238238 except ValueError :
239- # Invalid syntax for shlex (Probably due to missing closing quotes )
239+ # Invalid syntax for shlex (Probably due to missing closing quote )
240240 return completions
241241
242242 # Must have at least the command
@@ -247,7 +247,7 @@ def index_based_complete(text, line, begidx, endidx, index_dict):
247247
248248 # Check if this index does completions using an Iterable
249249 if isinstance (index_dict [index ], collections .Iterable ):
250- strs_to_match = ( index_dict [index ])
250+ strs_to_match = index_dict [index ]
251251 completions = [cur_str for cur_str in strs_to_match if cur_str .startswith (text )]
252252
253253 # If there is only 1 match and it's at the end of the line, then add a space
@@ -1259,25 +1259,23 @@ def complete(self, text, state):
12591259 # If begidx is greater than 0, then the cursor is past the command
12601260 if begidx > 0 :
12611261
1262- # Expand command shortcuts
1263- for (shortcut , expansion ) in self .shortcuts :
1264- if line .startswith (shortcut ):
1265- # If the next character after the shortcut isn't a space, then insert one
1266- shortcut_len = len (shortcut )
1267- if len (line ) == shortcut_len or line [shortcut_len ] != ' ' :
1268- expansion += ' '
1262+ # Parse the command line
1263+ command , args , expanded_line = self .parseline (line )
12691264
1270- # Expand the shortcut
1271- line = line .replace (shortcut , expansion , 1 )
1265+ # We overwrote line with a properly formatted but fully stripped version
1266+ # Restore the end spaces from the original since line is only supposed to be
1267+ # lstripped when passed to completer functions according to Python docs
1268+ rstripped_len = len (origline ) - len (origline .rstrip ())
1269+ expanded_line += ' ' * rstripped_len
12721270
1273- # Calculate difference in length to adjust indexes
1274- diff = len (expansion ) - len (shortcut )
1275- begidx += diff
1276- endidx += diff
1277- break
1271+ # Fix the index values if expanded_line has a different size than line
1272+ if len (expanded_line ) != len (line ):
1273+ diff = len ( expanded_line ) - len ( line )
1274+ begidx += diff
1275+ endidx += diff
12781276
1279- # Parse the command line
1280- command , args , foo = self . parseline ( line )
1277+ # Overwrite line to pass into completers
1278+ line = expanded_line
12811279
12821280 if command == '' :
12831281 compfunc = self .completedefault
@@ -1303,10 +1301,34 @@ def complete(self, text, state):
13031301 compfunc = getattr (self , 'complete_' + command )
13041302 except AttributeError :
13051303 compfunc = self .completedefault
1304+
1305+ # Call the completer function
1306+ self .completion_matches = compfunc (text , line , begidx , endidx )
1307+
13061308 else :
1307- compfunc = self .completenames
1309+ # Complete the command against command names and shortcuts. By design, shortcuts that start with
1310+ # symbols not in self.identchars won't be tab completed since they are handled in the above if
1311+ # statement. This includes shortcuts like: ?, !, @, @@
1312+ strs_to_match = []
1313+
1314+ # If a command has been started, then match against shortcuts. This keeps shortcuts out of the
1315+ # full list of commands that show up when tab completion is done on an empty line.
1316+ if len (line ) > 0 :
1317+ for (shortcut , expansion ) in self .shortcuts :
1318+ strs_to_match .append (shortcut )
1319+
1320+ # Get command names
1321+ do_text = 'do_' + text
1322+ strs_to_match .extend ([cur_name [3 :] for cur_name in self .get_names () if cur_name .startswith (do_text )])
1323+
1324+ # Perform matching
1325+ completions = [cur_str for cur_str in strs_to_match if cur_str .startswith (text )]
13081326
1309- self .completion_matches = compfunc (text , line , begidx , endidx )
1327+ # If there is only 1 match and it's at the end of the line, then add a space
1328+ if len (completions ) == 1 and endidx == len (line ):
1329+ completions [0 ] += ' '
1330+
1331+ self .completion_matches = completions
13101332
13111333 try :
13121334 return self .completion_matches [state ]
@@ -1399,13 +1421,25 @@ def parseline(self, line):
13991421 # Expand command shortcuts to the full command name
14001422 for (shortcut , expansion ) in self .shortcuts :
14011423 if line .startswith (shortcut ):
1402- line = line .replace (shortcut , expansion + ' ' , 1 )
1424+ # If the next character after the shortcut isn't a space, then insert one
1425+ shortcut_len = len (shortcut )
1426+ if len (line ) == shortcut_len or line [shortcut_len ] != ' ' :
1427+ expansion += ' '
1428+
1429+ # Expand the shortcut
1430+ line = line .replace (shortcut , expansion , 1 )
14031431 break
14041432
14051433 i , n = 0 , len (line )
14061434 while i < n and line [i ] in self .identchars :
14071435 i += 1
14081436 command , arg = line [:i ], line [i :].strip ()
1437+
1438+ # Make sure there is a space between the command and args
1439+ # This can occur when a character not in self.identchars bumps against the command (ex: help@)
1440+ if len (command ) > 0 and len (arg ) > 0 and line [len (command )] != ' ' :
1441+ line = line .replace (command , command + ' ' , 1 )
1442+
14091443 return command , arg , line
14101444
14111445 def onecmd_plus_hooks (self , line ):
@@ -2157,10 +2191,8 @@ def do_pyscript(self, arglist):
21572191 # Restore command line arguments to original state
21582192 sys .argv = orig_args
21592193
2160- @staticmethod
2161- def complete_pyscript (text , line , begidx , endidx ):
2162- """Readline tab-completion method for completing pyscript command."""
2163- return path_complete (text , line , begidx , endidx )
2194+ # Enable tab-completion for pyscript command
2195+ complete_pyscript = functools .partial (path_complete )
21642196
21652197 # Only include the do_ipy() method if IPython is available on the system
21662198 if ipython_available :
@@ -2301,10 +2333,8 @@ def do_edit(self, arglist):
23012333 else :
23022334 os .system ('"{}"' .format (self .editor ))
23032335
2304- @staticmethod
2305- def complete_edit (text , line , begidx , endidx ):
2306- """Readline tab-completion method for completing edit command."""
2307- return path_complete (text , line , begidx , endidx )
2336+ # Enable tab-completion for edit command
2337+ complete_edit = functools .partial (path_complete )
23082338
23092339 @property
23102340 def _current_script_dir (self ):
@@ -2392,10 +2422,8 @@ def do_load(self, arglist):
23922422
23932423 self ._script_dir .append (os .path .dirname (expanded_path ))
23942424
2395- @staticmethod
2396- def complete_load (text , line , begidx , endidx ):
2397- """Readline tab-completion method for completing load command."""
2398- return path_complete (text , line , begidx , endidx )
2425+ # Enable tab-completion for load command
2426+ complete_load = functools .partial (path_complete )
23992427
24002428 @staticmethod
24012429 def is_text_file (file_path ):
0 commit comments