@@ -1599,6 +1599,42 @@ def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only
15991599 :param dir_only: bool - only return directories
16001600 :return: List[str] - a list of possible tab completions
16011601 """
1602+
1603+ # Used to complete ~ and ~user strings
1604+ def complete_users ():
1605+
1606+ # We are returning ~user strings that resolve to directories,
1607+ # so don't append a space or quote in the case of a single result.
1608+ self .allow_appended_space = False
1609+ self .allow_closing_quote = False
1610+
1611+ users = []
1612+
1613+ # Windows lacks the pwd module so we can't get a list of users.
1614+ # Instead we will add a slash once the user enters text that
1615+ # resolves to an existing home directory.
1616+ if sys .platform .startswith ('win' ):
1617+ expanded_path = os .path .expanduser (text )
1618+ if os .path .isdir (expanded_path ):
1619+ users .append (text + os .path .sep )
1620+ else :
1621+ import pwd
1622+
1623+ # Iterate through a list of users from the password database
1624+ for cur_pw in pwd .getpwall ():
1625+
1626+ # Check if the user has an existing home dir
1627+ if os .path .isdir (cur_pw .pw_dir ):
1628+
1629+ # Add a ~ to the user to match against text
1630+ cur_user = '~' + cur_pw .pw_name
1631+ if cur_user .startswith (text ):
1632+ if add_trailing_sep_if_dir :
1633+ cur_user += os .path .sep
1634+ users .append (cur_user )
1635+
1636+ return users
1637+
16021638 # Determine if a trailing separator should be appended to directory completions
16031639 add_trailing_sep_if_dir = False
16041640 if endidx == len (line ) or (endidx < len (line ) and line [endidx ] != os .path .sep ):
@@ -1608,9 +1644,9 @@ def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only
16081644 cwd = os .getcwd ()
16091645 cwd_added = False
16101646
1611- # Used to replace ~ in the final results
1612- user_path = os . path . expanduser ( '~' )
1613- tilde_expanded = False
1647+ # Used to replace expanded user path in final result
1648+ orig_tilde_path = ''
1649+ expanded_tilde_path = ''
16141650
16151651 # If the search text is blank, then search in the CWD for *
16161652 if not text :
@@ -1623,35 +1659,30 @@ def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only
16231659 if wildcard in text :
16241660 return []
16251661
1626- # Used if we need to prepend a directory to the search string
1627- dirname = ' '
1662+ # Start the search string
1663+ search_str = text + '* '
16281664
1629- # If the user only entered a '~', then complete it with a slash
1630- if text == '~' :
1631- # This is a directory, so don't add a space or quote
1632- self .allow_appended_space = False
1633- self .allow_closing_quote = False
1634- return [text + os .path .sep ]
1665+ # Handle tilde expansion and completion
1666+ if text .startswith ('~' ):
1667+ sep_index = text .find (os .path .sep , 1 )
16351668
1636- elif text .startswith ('~' ):
1637- # Tilde without separator between path is invalid
1638- if not text .startswith ('~' + os .path .sep ):
1639- return []
1669+ # If there is no slash, then the user is still completing the user after the tilde
1670+ if sep_index == - 1 :
1671+ return complete_users ()
1672+
1673+ # Otherwise expand the user dir
1674+ else :
1675+ search_str = os .path .expanduser (search_str )
16401676
1641- # Mark that we are expanding a tilde
1642- tilde_expanded = True
1677+ # Get what we need to restore the original tilde path later
1678+ orig_tilde_path = text [:sep_index ]
1679+ expanded_tilde_path = os .path .expanduser (orig_tilde_path )
16431680
16441681 # If the search text does not have a directory, then use the cwd
16451682 elif not os .path .dirname (text ):
1646- dirname = os .getcwd ()
1683+ search_str = os .path . join ( os . getcwd (), search_str )
16471684 cwd_added = True
16481685
1649- # Build the search string
1650- search_str = os .path .join (dirname , text + '*' )
1651-
1652- # Expand "~" to the real user directory
1653- search_str = os .path .expanduser (search_str )
1654-
16551686 # Find all matching path completions
16561687 matches = glob .glob (search_str )
16571688
@@ -1662,7 +1693,7 @@ def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only
16621693 matches = [c for c in matches if os .path .isdir (c )]
16631694
16641695 # Don't append a space or closing quote to directory
1665- if len (matches ) == 1 and not os .path .isfile (matches [0 ]):
1696+ if len (matches ) == 1 and os .path .isdir (matches [0 ]):
16661697 self .allow_appended_space = False
16671698 self .allow_closing_quote = False
16681699
@@ -1677,13 +1708,13 @@ def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only
16771708 matches [index ] += os .path .sep
16781709 self .display_matches [index ] += os .path .sep
16791710
1680- # Remove cwd if it was added
1711+ # Remove cwd if it was added to match the text readline expects
16811712 if cwd_added :
16821713 matches = [cur_path .replace (cwd + os .path .sep , '' , 1 ) for cur_path in matches ]
16831714
1684- # Restore a tilde if we expanded one
1685- if tilde_expanded :
1686- matches = [cur_path .replace (user_path , '~' , 1 ) for cur_path in matches ]
1715+ # Restore the tilde string if we expanded one to match the text readline expects
1716+ if expanded_tilde_path :
1717+ matches = [cur_path .replace (expanded_tilde_path , orig_tilde_path , 1 ) for cur_path in matches ]
16871718
16881719 return matches
16891720
@@ -1732,7 +1763,7 @@ def shell_cmd_complete(self, text, line, begidx, endidx, complete_blank=False):
17321763 return []
17331764
17341765 # If there are no path characters in the search text, then do shell command completion in the user's path
1735- if os .path .sep not in text :
1766+ if not text . startswith ( '~' ) and os .path .sep not in text :
17361767 return self .get_exes_in_path (text )
17371768
17381769 # Otherwise look for executables in the given path
@@ -1806,9 +1837,6 @@ def _pad_matches_to_display(matches_to_display):
18061837 :param matches_to_display: the matches being padded
18071838 :return: the padded matches and length of padding that was added
18081839 """
1809- if rl_type == RlType .NONE :
1810- return matches_to_display , 0
1811-
18121840 if rl_type == RlType .GNU :
18131841 # Add 2 to the padding of 2 that readline uses for a total of 4.
18141842 padding = 2 * ' '
@@ -1817,6 +1845,9 @@ def _pad_matches_to_display(matches_to_display):
18171845 # Add 3 to the padding of 1 that pyreadline uses for a total of 4.
18181846 padding = 3 * ' '
18191847
1848+ else :
1849+ return matches_to_display , 0
1850+
18201851 return [cur_match + padding for cur_match in matches_to_display ], len (padding )
18211852
18221853 def _display_matches_gnu_readline (self , substitution , matches , longest_match_length ):
0 commit comments