@@ -494,13 +494,20 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, persistent_histor
494
494
# will be added if there is an unmatched opening quote
495
495
self .allow_closing_quote = True
496
496
497
+ # An optional header that prints above the tab-completion suggestions
498
+ self .completion_header = ''
499
+
497
500
# Use this list if you are completing strings that contain a common delimiter and you only want to
498
501
# display the final portion of the matches as the tab-completion suggestions. The full matches
499
502
# still must be returned from your completer function. For an example, look at path_complete()
500
503
# which uses this to show only the basename of paths as the suggestions. delimiter_complete() also
501
504
# populates this list.
502
505
self .display_matches = []
503
506
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
+
504
511
# ----- Methods related to presenting output to the user -----
505
512
506
513
@property
@@ -657,7 +664,9 @@ def reset_completion_defaults(self):
657
664
"""
658
665
self .allow_appended_space = True
659
666
self .allow_closing_quote = True
667
+ self .completion_header = ''
660
668
self .display_matches = []
669
+ self .matches_delimited = False
661
670
662
671
if rl_type == RlType .GNU :
663
672
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
836
845
837
846
# Display only the portion of the match that's being completed based on delimiter
838
847
if matches :
848
+ # Set this to True for proper quoting of matches with spaces
849
+ self .matches_delimited = True
839
850
840
851
# Get the common beginning for the matches
841
852
common_prefix = os .path .commonprefix (matches )
@@ -1037,6 +1048,9 @@ def complete_users():
1037
1048
search_str = os .path .join (os .getcwd (), search_str )
1038
1049
cwd_added = True
1039
1050
1051
+ # Set this to True for proper quoting of paths with spaces
1052
+ self .matches_delimited = True
1053
+
1040
1054
# Find all matching path completions
1041
1055
matches = glob .glob (search_str )
1042
1056
@@ -1245,6 +1259,10 @@ def _display_matches_gnu_readline(self, substitution, matches, longest_match_len
1245
1259
strings_array [1 :- 1 ] = encoded_matches
1246
1260
strings_array [- 1 ] = None
1247
1261
1262
+ # Print the header if one exists
1263
+ if self .completion_header :
1264
+ sys .stdout .write ('\n ' + self .completion_header )
1265
+
1248
1266
# Call readline's display function
1249
1267
# rl_display_match_list(strings_array, number of completion matches, longest match length)
1250
1268
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
1270
1288
# Add padding for visual appeal
1271
1289
matches_to_display , _ = self ._pad_matches_to_display (matches_to_display )
1272
1290
1291
+ # Print the header if one exists
1292
+ if self .completion_header :
1293
+ readline .rl .mode .console .write ('\n ' + self .completion_header )
1294
+
1273
1295
# Display matches using actual display function. This also redraws the prompt and line.
1274
1296
orig_pyreadline_display (matches_to_display )
1275
1297
@@ -1309,7 +1331,7 @@ def complete(self, text, state):
1309
1331
# from text and update the indexes. This only applies if we are at the the beginning of the line.
1310
1332
shortcut_to_restore = ''
1311
1333
if begidx == 0 :
1312
- for (shortcut , expansion ) in self .shortcuts :
1334
+ for (shortcut , _ ) in self .shortcuts :
1313
1335
if text .startswith (shortcut ):
1314
1336
# Save the shortcut to restore later
1315
1337
shortcut_to_restore = shortcut
@@ -1414,13 +1436,7 @@ def complete(self, text, state):
1414
1436
display_matches_set = set (self .display_matches )
1415
1437
self .display_matches = list (display_matches_set )
1416
1438
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 :
1424
1440
# Since self.display_matches is empty, set it to self.completion_matches
1425
1441
# before we alter them. That way the suggestions will reflect how we parsed
1426
1442
# the token being completed and not how readline did.
@@ -1435,7 +1451,7 @@ def complete(self, text, state):
1435
1451
# This is the tab completion text that will appear on the command line.
1436
1452
common_prefix = os .path .commonprefix (self .completion_matches )
1437
1453
1438
- if matches_delimited :
1454
+ if self . matches_delimited :
1439
1455
# Check if any portion of the display matches appears in the tab completion
1440
1456
display_prefix = os .path .commonprefix (self .display_matches )
1441
1457
@@ -1696,7 +1712,7 @@ def onecmd_plus_hooks(self, line):
1696
1712
if self .timing :
1697
1713
self .pfeedback ('Elapsed: %s' % str (datetime .datetime .now () - timestart ))
1698
1714
finally :
1699
- if self .allow_redirection :
1715
+ if self .allow_redirection and self . redirecting :
1700
1716
self ._restore_output (statement )
1701
1717
except EmptyStatement :
1702
1718
pass
@@ -1840,7 +1856,11 @@ def _redirect_output(self, statement):
1840
1856
# REDIRECTION_APPEND or REDIRECTION_OUTPUT
1841
1857
if statement .output == constants .REDIRECTION_APPEND :
1842
1858
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
1844
1864
else :
1845
1865
# going to a paste buffer
1846
1866
sys .stdout = self .stdout = tempfile .TemporaryFile (mode = "w+" )
@@ -2366,7 +2386,7 @@ def select(self, opts, prompt='Your choice? '):
2366
2386
fulloptions .append ((opt [0 ], opt [1 ]))
2367
2387
except IndexError :
2368
2388
fulloptions .append ((opt [0 ], opt [0 ]))
2369
- for (idx , (value , text )) in enumerate (fulloptions ):
2389
+ for (idx , (_ , text )) in enumerate (fulloptions ):
2370
2390
self .poutput (' %2d. %s\n ' % (idx + 1 , text ))
2371
2391
while True :
2372
2392
response = input (prompt )
@@ -2878,16 +2898,19 @@ def _generate_transcript(self, history, transcript_file):
2878
2898
self .echo = saved_echo
2879
2899
2880
2900
# 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 )
2887
2906
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 ))
2891
2914
2892
2915
@with_argument_list
2893
2916
def do_edit (self , arglist ):
0 commit comments