118
118
from .history import (
119
119
History ,
120
120
HistoryItem ,
121
+ single_line_format ,
121
122
)
122
123
from .parsing import (
123
124
Macro ,
@@ -2227,7 +2228,7 @@ def complete( # type: ignore[override]
2227
2228
# Check if we are completing a multiline command
2228
2229
if self ._at_continuation_prompt :
2229
2230
# lstrip and prepend the previously typed portion of this multiline command
2230
- lstripped_previous = self ._multiline_in_progress .lstrip (). replace ( constants . LINE_FEED , ' ' )
2231
+ lstripped_previous = self ._multiline_in_progress .lstrip ()
2231
2232
line = lstripped_previous + readline .get_line_buffer ()
2232
2233
2233
2234
# Increment the indexes to account for the prepended text
@@ -2503,7 +2504,13 @@ def parseline(self, line: str) -> Tuple[str, str, str]:
2503
2504
return statement .command , statement .args , statement .command_and_args
2504
2505
2505
2506
def onecmd_plus_hooks (
2506
- self , line : str , * , add_to_history : bool = True , raise_keyboard_interrupt : bool = False , py_bridge_call : bool = False
2507
+ self ,
2508
+ line : str ,
2509
+ * ,
2510
+ add_to_history : bool = True ,
2511
+ raise_keyboard_interrupt : bool = False ,
2512
+ py_bridge_call : bool = False ,
2513
+ orig_rl_history_length : Optional [int ] = None ,
2507
2514
) -> bool :
2508
2515
"""Top-level function called by cmdloop() to handle parsing a line and running the command and all of its hooks.
2509
2516
@@ -2515,6 +2522,9 @@ def onecmd_plus_hooks(
2515
2522
:param py_bridge_call: This should only ever be set to True by PyBridge to signify the beginning
2516
2523
of an app() call from Python. It is used to enable/disable the storage of the
2517
2524
command's stdout.
2525
+ :param orig_rl_history_length: Optional length of the readline history before the current command was typed.
2526
+ This is used to assist in combining multiline readline history entries and is only
2527
+ populated by cmd2. Defaults to None.
2518
2528
:return: True if running of commands should stop
2519
2529
"""
2520
2530
import datetime
@@ -2524,7 +2534,7 @@ def onecmd_plus_hooks(
2524
2534
2525
2535
try :
2526
2536
# Convert the line into a Statement
2527
- statement = self ._input_line_to_statement (line )
2537
+ statement = self ._input_line_to_statement (line , orig_rl_history_length = orig_rl_history_length )
2528
2538
2529
2539
# call the postparsing hooks
2530
2540
postparsing_data = plugin .PostparsingData (False , statement )
@@ -2738,11 +2748,14 @@ def _complete_statement(self, line: str) -> Statement:
2738
2748
raise EmptyStatement
2739
2749
return statement
2740
2750
2741
- def _input_line_to_statement (self , line : str ) -> Statement :
2751
+ def _input_line_to_statement (self , line : str , * , orig_rl_history_length : Optional [ int ] = None ) -> Statement :
2742
2752
"""
2743
2753
Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved
2744
2754
2745
2755
:param line: the line being parsed
2756
+ :param orig_rl_history_length: Optional length of the readline history before the current command was typed.
2757
+ This is used to assist in combining multiline readline history entries and is only
2758
+ populated by cmd2. Defaults to None.
2746
2759
:return: parsed command line as a Statement
2747
2760
:raises: Cmd2ShlexError if a shlex error occurs (e.g. No closing quotation)
2748
2761
:raises: EmptyStatement when the resulting Statement is blank
@@ -2755,10 +2768,23 @@ def _input_line_to_statement(self, line: str) -> Statement:
2755
2768
# Make sure all input has been read and convert it to a Statement
2756
2769
statement = self ._complete_statement (line )
2757
2770
2758
- # Save the fully entered line if this is the first loop iteration
2771
+ # If this is the first loop iteration, save the original line and if necessary,
2772
+ # combine multiline history entries into single readline history item.
2759
2773
if orig_line is None :
2760
2774
orig_line = statement .raw
2761
2775
2776
+ if orig_rl_history_length is not None and statement .multiline_command :
2777
+ # Remove all lines added to history for this command
2778
+ while readline .get_current_history_length () > orig_rl_history_length :
2779
+ readline .remove_history_item (readline .get_current_history_length () - 1 )
2780
+
2781
+ # Combine the lines of this command
2782
+ combined_command = single_line_format (statement )
2783
+
2784
+ # If combined_command is different than the previous history item, then add it
2785
+ if orig_rl_history_length == 0 or combined_command != readline .get_history_item (orig_rl_history_length ):
2786
+ readline .add_history (combined_command )
2787
+
2762
2788
# Check if this command matches a macro and wasn't already processed to avoid an infinite loop
2763
2789
if statement .command in self .macros .keys () and statement .command not in used_macros :
2764
2790
used_macros .append (statement .command )
@@ -3111,7 +3137,7 @@ def configure_readline() -> None:
3111
3137
nonlocal saved_history
3112
3138
nonlocal parser
3113
3139
3114
- if readline_configured : # pragma: no cover
3140
+ if readline_configured or rl_type == RlType . NONE : # pragma: no cover
3115
3141
return
3116
3142
3117
3143
# Configure tab completion
@@ -3163,7 +3189,7 @@ def complete_none(text: str, state: int) -> Optional[str]: # pragma: no cover
3163
3189
def restore_readline () -> None :
3164
3190
"""Restore readline tab completion and history"""
3165
3191
nonlocal readline_configured
3166
- if not readline_configured : # pragma: no cover
3192
+ if not readline_configured or rl_type == RlType . NONE : # pragma: no cover
3167
3193
return
3168
3194
3169
3195
if self ._completion_supported ():
@@ -3310,6 +3336,13 @@ def _cmdloop(self) -> None:
3310
3336
self ._startup_commands .clear ()
3311
3337
3312
3338
while not stop :
3339
+ # Used in building multiline readline history entries. Only applies
3340
+ # when command line is read using input() in a terminal.
3341
+ if rl_type != RlType .NONE and self .use_rawinput and sys .stdin .isatty ():
3342
+ orig_rl_history_length = readline .get_current_history_length ()
3343
+ else :
3344
+ orig_rl_history_length = None
3345
+
3313
3346
# Get commands from user
3314
3347
try :
3315
3348
line = self ._read_command_line (self .prompt )
@@ -3318,7 +3351,7 @@ def _cmdloop(self) -> None:
3318
3351
line = ''
3319
3352
3320
3353
# Run the command along with all associated pre and post hooks
3321
- stop = self .onecmd_plus_hooks (line )
3354
+ stop = self .onecmd_plus_hooks (line , orig_rl_history_length = orig_rl_history_length )
3322
3355
finally :
3323
3356
# Get sigint protection while we restore readline settings
3324
3357
with self .sigint_protection :
@@ -4873,13 +4906,13 @@ def _initialize_history(self, hist_file: str) -> None:
4873
4906
if rl_type != RlType .NONE :
4874
4907
last = None
4875
4908
for item in self .history :
4876
- # Break the command into its individual lines
4877
- for line in item . raw . splitlines ():
4878
- # readline only adds a single entry for multiple sequential identical lines
4879
- # so we emulate that behavior here
4880
- if line != last :
4881
- readline .add_history (line )
4882
- last = line
4909
+ formatted_command = single_line_format ( item . statement )
4910
+
4911
+ # readline only adds a single entry for multiple sequential identical lines
4912
+ # so we emulate that behavior here
4913
+ if formatted_command != last :
4914
+ readline .add_history (formatted_command )
4915
+ last = formatted_command
4883
4916
4884
4917
def _persist_history (self ) -> None :
4885
4918
"""Write history out to the persistent history file as compressed JSON"""
0 commit comments