@@ -494,13 +494,20 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, persistent_histor
494494 # will be added if there is an unmatched opening quote
495495 self .allow_closing_quote = True
496496
497+ # An optional header that prints above the tab-completion suggestions
498+ self .completion_header = ''
499+
497500 # Use this list if you are completing strings that contain a common delimiter and you only want to
498501 # display the final portion of the matches as the tab-completion suggestions. The full matches
499502 # still must be returned from your completer function. For an example, look at path_complete()
500503 # which uses this to show only the basename of paths as the suggestions. delimiter_complete() also
501504 # populates this list.
502505 self .display_matches = []
503506
507+ # Used by functions like path_complete() and delimiter_complete() to properly
508+ # quote matches that are completed in a delimited fashion
509+ self .matches_delimited = False
510+
504511 # ----- Methods related to presenting output to the user -----
505512
506513 @property
@@ -657,7 +664,9 @@ def reset_completion_defaults(self):
657664 """
658665 self .allow_appended_space = True
659666 self .allow_closing_quote = True
667+ self .completion_header = ''
660668 self .display_matches = []
669+ self .matches_delimited = False
661670
662671 if rl_type == RlType .GNU :
663672 readline .set_completion_display_matches_hook (self ._display_matches_gnu_readline )
@@ -836,6 +845,8 @@ def delimiter_complete(self, text, line, begidx, endidx, match_against, delimite
836845
837846 # Display only the portion of the match that's being completed based on delimiter
838847 if matches :
848+ # Set this to True for proper quoting of matches with spaces
849+ self .matches_delimited = True
839850
840851 # Get the common beginning for the matches
841852 common_prefix = os .path .commonprefix (matches )
@@ -1037,6 +1048,9 @@ def complete_users():
10371048 search_str = os .path .join (os .getcwd (), search_str )
10381049 cwd_added = True
10391050
1051+ # Set this to True for proper quoting of paths with spaces
1052+ self .matches_delimited = True
1053+
10401054 # Find all matching path completions
10411055 matches = glob .glob (search_str )
10421056
@@ -1245,6 +1259,10 @@ def _display_matches_gnu_readline(self, substitution, matches, longest_match_len
12451259 strings_array [1 :- 1 ] = encoded_matches
12461260 strings_array [- 1 ] = None
12471261
1262+ # Print the header if one exists
1263+ if self .completion_header :
1264+ sys .stdout .write ('\n ' + self .completion_header )
1265+
12481266 # Call readline's display function
12491267 # rl_display_match_list(strings_array, number of completion matches, longest match length)
12501268 readline_lib .rl_display_match_list (strings_array , len (encoded_matches ), longest_match_length )
@@ -1270,6 +1288,10 @@ def _display_matches_pyreadline(self, matches): # pragma: no cover
12701288 # Add padding for visual appeal
12711289 matches_to_display , _ = self ._pad_matches_to_display (matches_to_display )
12721290
1291+ # Print the header if one exists
1292+ if self .completion_header :
1293+ readline .rl .mode .console .write ('\n ' + self .completion_header )
1294+
12731295 # Display matches using actual display function. This also redraws the prompt and line.
12741296 orig_pyreadline_display (matches_to_display )
12751297
@@ -1309,7 +1331,7 @@ def complete(self, text, state):
13091331 # from text and update the indexes. This only applies if we are at the the beginning of the line.
13101332 shortcut_to_restore = ''
13111333 if begidx == 0 :
1312- for (shortcut , expansion ) in self .shortcuts :
1334+ for (shortcut , _ ) in self .shortcuts :
13131335 if text .startswith (shortcut ):
13141336 # Save the shortcut to restore later
13151337 shortcut_to_restore = shortcut
@@ -1414,13 +1436,7 @@ def complete(self, text, state):
14141436 display_matches_set = set (self .display_matches )
14151437 self .display_matches = list (display_matches_set )
14161438
1417- # Check if display_matches has been used. If so, then matches
1418- # on delimited strings like paths was done.
1419- if self .display_matches :
1420- matches_delimited = True
1421- else :
1422- matches_delimited = False
1423-
1439+ if not self .display_matches :
14241440 # Since self.display_matches is empty, set it to self.completion_matches
14251441 # before we alter them. That way the suggestions will reflect how we parsed
14261442 # the token being completed and not how readline did.
@@ -1435,7 +1451,7 @@ def complete(self, text, state):
14351451 # This is the tab completion text that will appear on the command line.
14361452 common_prefix = os .path .commonprefix (self .completion_matches )
14371453
1438- if matches_delimited :
1454+ if self . matches_delimited :
14391455 # Check if any portion of the display matches appears in the tab completion
14401456 display_prefix = os .path .commonprefix (self .display_matches )
14411457
@@ -1696,7 +1712,7 @@ def onecmd_plus_hooks(self, line):
16961712 if self .timing :
16971713 self .pfeedback ('Elapsed: %s' % str (datetime .datetime .now () - timestart ))
16981714 finally :
1699- if self .allow_redirection :
1715+ if self .allow_redirection and self . redirecting :
17001716 self ._restore_output (statement )
17011717 except EmptyStatement :
17021718 pass
@@ -1840,7 +1856,11 @@ def _redirect_output(self, statement):
18401856 # REDIRECTION_APPEND or REDIRECTION_OUTPUT
18411857 if statement .output == constants .REDIRECTION_APPEND :
18421858 mode = 'a'
1843- sys .stdout = self .stdout = open (statement .output_to , mode )
1859+ try :
1860+ sys .stdout = self .stdout = open (statement .output_to , mode )
1861+ except OSError as ex :
1862+ self .perror ('Not Redirecting because - {}' .format (ex ), traceback_war = False )
1863+ self .redirecting = False
18441864 else :
18451865 # going to a paste buffer
18461866 sys .stdout = self .stdout = tempfile .TemporaryFile (mode = "w+" )
@@ -2366,7 +2386,7 @@ def select(self, opts, prompt='Your choice? '):
23662386 fulloptions .append ((opt [0 ], opt [1 ]))
23672387 except IndexError :
23682388 fulloptions .append ((opt [0 ], opt [0 ]))
2369- for (idx , (value , text )) in enumerate (fulloptions ):
2389+ for (idx , (_ , text )) in enumerate (fulloptions ):
23702390 self .poutput (' %2d. %s\n ' % (idx + 1 , text ))
23712391 while True :
23722392 response = input (prompt )
@@ -2878,16 +2898,19 @@ def _generate_transcript(self, history, transcript_file):
28782898 self .echo = saved_echo
28792899
28802900 # finally, we can write the transcript out to the file
2881- with open (transcript_file , 'w' ) as fout :
2882- fout .write (transcript )
2883-
2884- # and let the user know what we did
2885- if len (history ) > 1 :
2886- plural = 'commands and their outputs'
2901+ try :
2902+ with open (transcript_file , 'w' ) as fout :
2903+ fout .write (transcript )
2904+ except OSError as ex :
2905+ self .perror ('Failed to save transcript: {}' .format (ex ), traceback_war = False )
28872906 else :
2888- plural = 'command and its output'
2889- msg = '{} {} saved to transcript file {!r}'
2890- self .pfeedback (msg .format (len (history ), plural , transcript_file ))
2907+ # and let the user know what we did
2908+ if len (history ) > 1 :
2909+ plural = 'commands and their outputs'
2910+ else :
2911+ plural = 'command and its output'
2912+ msg = '{} {} saved to transcript file {!r}'
2913+ self .pfeedback (msg .format (len (history ), plural , transcript_file ))
28912914
28922915 @with_argument_list
28932916 def do_edit (self , arglist ):
0 commit comments