118118from  .history  import  (
119119    History ,
120120    HistoryItem ,
121+     single_line_format ,
121122)
122123from  .parsing  import  (
123124    Macro ,
@@ -2227,7 +2228,7 @@ def complete(  # type: ignore[override]
22272228                # Check if we are completing a multiline command 
22282229                if  self ._at_continuation_prompt :
22292230                    # 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 ()
22312232                    line  =  lstripped_previous  +  readline .get_line_buffer ()
22322233
22332234                    # Increment the indexes to account for the prepended text 
@@ -2503,7 +2504,13 @@ def parseline(self, line: str) -> Tuple[str, str, str]:
25032504        return  statement .command , statement .args , statement .command_and_args 
25042505
25052506    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 ,
25072514    ) ->  bool :
25082515        """Top-level function called by cmdloop() to handle parsing a line and running the command and all of its hooks. 
25092516
@@ -2515,6 +2522,9 @@ def onecmd_plus_hooks(
25152522        :param py_bridge_call: This should only ever be set to True by PyBridge to signify the beginning 
25162523                               of an app() call from Python. It is used to enable/disable the storage of the 
25172524                               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. 
25182528        :return: True if running of commands should stop 
25192529        """ 
25202530        import  datetime 
@@ -2524,7 +2534,7 @@ def onecmd_plus_hooks(
25242534
25252535        try :
25262536            # 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 )
25282538
25292539            # call the postparsing hooks 
25302540            postparsing_data  =  plugin .PostparsingData (False , statement )
@@ -2678,7 +2688,7 @@ def runcmds_plus_hooks(
26782688
26792689        return  False 
26802690
2681-     def  _complete_statement (self , line : str ) ->  Statement :
2691+     def  _complete_statement (self , line : str ,  * ,  orig_rl_history_length :  Optional [ int ]  =   None ) ->  Statement :
26822692        """Keep accepting lines of input until the command is complete. 
26832693
26842694        There is some pretty hacky code here to handle some quirks of 
@@ -2687,10 +2697,29 @@ def _complete_statement(self, line: str) -> Statement:
26872697        backwards compatibility with the standard library version of cmd. 
26882698
26892699        :param line: the line being parsed 
2700+         :param orig_rl_history_length: Optional length of the readline history before the current command was typed. 
2701+                                        This is used to assist in combining multiline readline history entries and is only 
2702+                                        populated by cmd2. Defaults to None. 
26902703        :return: the completed Statement 
26912704        :raises: Cmd2ShlexError if a shlex error occurs (e.g. No closing quotation) 
26922705        :raises: EmptyStatement when the resulting Statement is blank 
26932706        """ 
2707+ 
2708+         def  combine_rl_history (statement : Statement ) ->  None :
2709+             """Combine all lines of a multiline command into a single readline history entry""" 
2710+             if  orig_rl_history_length  is  None  or  not  statement .multiline_command :
2711+                 return 
2712+ 
2713+             # Remove all previous lines added to history for this command 
2714+             while  readline .get_current_history_length () >  orig_rl_history_length :
2715+                 readline .remove_history_item (readline .get_current_history_length () -  1 )
2716+ 
2717+             formatted_command  =  single_line_format (statement )
2718+ 
2719+             # If formatted command is different than the previous history item, add it 
2720+             if  orig_rl_history_length  ==  0  or  formatted_command  !=  readline .get_history_item (orig_rl_history_length ):
2721+                 readline .add_history (formatted_command )
2722+ 
26942723        while  True :
26952724            try :
26962725                statement  =  self .statement_parser .parse (line )
@@ -2702,7 +2731,7 @@ def _complete_statement(self, line: str) -> Statement:
27022731                    # so we are done 
27032732                    break 
27042733            except  Cmd2ShlexError :
2705-                 # we have unclosed quotation marks, lets  parse only the command 
2734+                 # we have an  unclosed quotation mark, let's  parse only the command 
27062735                # and see if it's a multiline 
27072736                statement  =  self .statement_parser .parse_command_only (line )
27082737                if  not  statement .multiline_command :
@@ -2727,6 +2756,12 @@ def _complete_statement(self, line: str) -> Statement:
27272756                    nextline  =  '\n ' 
27282757                    self .poutput (nextline )
27292758                line  =  f'{ self ._multiline_in_progress } { nextline }  ' 
2759+ 
2760+                 # Combine all lines of this multiline command as we go. 
2761+                 if  nextline :
2762+                     statement  =  self .statement_parser .parse_command_only (line )
2763+                     combine_rl_history (statement )
2764+ 
27302765            except  KeyboardInterrupt :
27312766                self .poutput ('^C' )
27322767                statement  =  self .statement_parser .parse ('' )
@@ -2736,13 +2771,20 @@ def _complete_statement(self, line: str) -> Statement:
27362771
27372772        if  not  statement .command :
27382773            raise  EmptyStatement 
2774+         else :
2775+             # If necessary, update history with completed multiline command. 
2776+             combine_rl_history (statement )
2777+ 
27392778        return  statement 
27402779
2741-     def  _input_line_to_statement (self , line : str ) ->  Statement :
2780+     def  _input_line_to_statement (self , line : str ,  * ,  orig_rl_history_length :  Optional [ int ]  =   None ) ->  Statement :
27422781        """ 
27432782        Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved 
27442783
27452784        :param line: the line being parsed 
2785+         :param orig_rl_history_length: Optional length of the readline history before the current command was typed. 
2786+                                        This is used to assist in combining multiline readline history entries and is only 
2787+                                        populated by cmd2. Defaults to None. 
27462788        :return: parsed command line as a Statement 
27472789        :raises: Cmd2ShlexError if a shlex error occurs (e.g. No closing quotation) 
27482790        :raises: EmptyStatement when the resulting Statement is blank 
@@ -2753,11 +2795,13 @@ def _input_line_to_statement(self, line: str) -> Statement:
27532795        # Continue until all macros are resolved 
27542796        while  True :
27552797            # Make sure all input has been read and convert it to a Statement 
2756-             statement  =  self ._complete_statement (line )
2798+             statement  =  self ._complete_statement (line ,  orig_rl_history_length = orig_rl_history_length )
27572799
2758-             # Save the fully entered line if this is the first loop iteration 
2800+             # If this is the first loop iteration, save the original line and stop 
2801+             # combining multiline history entries in the remaining iterations. 
27592802            if  orig_line  is  None :
27602803                orig_line  =  statement .raw 
2804+                 orig_rl_history_length  =  None 
27612805
27622806            # Check if this command matches a macro and wasn't already processed to avoid an infinite loop 
27632807            if  statement .command  in  self .macros .keys () and  statement .command  not  in   used_macros :
@@ -3111,7 +3155,7 @@ def configure_readline() -> None:
31113155            nonlocal  saved_history 
31123156            nonlocal  parser 
31133157
3114-             if  readline_configured :  # pragma: no cover 
3158+             if  readline_configured   or   rl_type   ==   RlType . NONE :  # pragma: no cover 
31153159                return 
31163160
31173161            # Configure tab completion 
@@ -3163,7 +3207,7 @@ def complete_none(text: str, state: int) -> Optional[str]:  # pragma: no cover
31633207        def  restore_readline () ->  None :
31643208            """Restore readline tab completion and history""" 
31653209            nonlocal  readline_configured 
3166-             if  not  readline_configured :  # pragma: no cover 
3210+             if  not  readline_configured   or   rl_type   ==   RlType . NONE :  # pragma: no cover 
31673211                return 
31683212
31693213            if  self ._completion_supported ():
@@ -3310,6 +3354,13 @@ def _cmdloop(self) -> None:
33103354            self ._startup_commands .clear ()
33113355
33123356            while  not  stop :
3357+                 # Used in building multiline readline history entries. Only applies 
3358+                 # when command line is read by input() in a terminal. 
3359+                 if  rl_type  !=  RlType .NONE  and  self .use_rawinput  and  sys .stdin .isatty ():
3360+                     orig_rl_history_length  =  readline .get_current_history_length ()
3361+                 else :
3362+                     orig_rl_history_length  =  None 
3363+ 
33133364                # Get commands from user 
33143365                try :
33153366                    line  =  self ._read_command_line (self .prompt )
@@ -3318,7 +3369,7 @@ def _cmdloop(self) -> None:
33183369                    line  =  '' 
33193370
33203371                # Run the command along with all associated pre and post hooks 
3321-                 stop  =  self .onecmd_plus_hooks (line )
3372+                 stop  =  self .onecmd_plus_hooks (line ,  orig_rl_history_length = orig_rl_history_length )
33223373        finally :
33233374            # Get sigint protection while we restore readline settings 
33243375            with  self .sigint_protection :
@@ -4871,15 +4922,13 @@ def _initialize_history(self, hist_file: str) -> None:
48714922
48724923        # Populate readline history 
48734924        if  rl_type  !=  RlType .NONE :
4874-             last  =  None 
48754925            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 
4926+                 formatted_command  =  single_line_format (item .statement )
4927+ 
4928+                 # If formatted command is different than the previous history item, add it 
4929+                 cur_history_length  =  readline .get_current_history_length ()
4930+                 if  cur_history_length  ==  0  or  formatted_command  !=  readline .get_history_item (cur_history_length ):
4931+                     readline .add_history (formatted_command )
48834932
48844933    def  _persist_history (self ) ->  None :
48854934        """Write history out to the persistent history file as compressed JSON""" 
0 commit comments