Skip to content
This repository

reenable multiline history for terminals #838

Merged
merged 3 commits into from over 2 years ago

4 participants

Julian Taylor Thomas Kluyver Min RK Fernando Perez
Julian Taylor
Collaborator

Add configuration variable InteractiveShell.multiline_history.
If it is True cells spanning multiple lines will be saved in history
as a single entry instead of one entry per line, as was the case in
ipython < 0.11.
closes gh-571

IPython/frontend/terminal/interactiveshell.py
... ...
@@ -229,6 +229,15 @@ class TerminalInteractiveShell(InteractiveShell):
229 229
                     # handling seems rather unpredictable...
230 230
                     self.write("\nKeyboardInterrupt in interact()\n")
231 231
 
  232
+    def _store_multiline_history(self, source_raw):
  233
+        """Store multiple lines as a single entry in history"""
  234
+        if self.multiline_history and self.has_readline:
  235
+            hlen = self.readline.get_current_history_length()
  236
+            lines = len(source_raw.splitlines())
  237
+            for i in range(1, min(hlen, lines) + 1):
  238
+                self.readline.remove_history_item(hlen - i)
3
Thomas Kluyver Collaborator

I assume you've tested this, but surely if you remove item 41 (for instance), the item that was previously 42 is now 41? So shouldn't we just remove an item from the same slot n times?

Julian Taylor Collaborator

you always remove from the end of the history so in effect its the same

Thomas Kluyver Collaborator

Oh I see. I'd overlooked that it was counting backwards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Julian Taylor juliantaylor closed this October 05, 2011
Julian Taylor
Collaborator

please don't merge yet I noticed an issue that it deletes history when you recall history and execute it again as the its one line in history but it removes linecount items.

This can be solved by counting the times it accepts more and removing that amount of items.
I'll test that a bit and reopen.

Thomas Kluyver
Collaborator

Perhaps in the interact loop, you could keep a record of the readline history length at the start of the current cell, then remove any extra entries after that.

Julian Taylor juliantaylor reopened this October 06, 2011
Thomas Kluyver
Collaborator

Could I suggest clarifying variable names a bit, for people who read this code in the future. In particular, hlen (hlen_before_cell?), and _store_multiline_history (_replace_rlhist_multiline?).

Min RK
Owner

Is this something that can be easily tested?

Thomas Kluyver
Collaborator

I think we could test calling the function (_replace_rlhist_multiline) and checking the modifications it makes via readline.get_history_item. Obviously the actual user interaction is a bit trickier to test.

Thomas Kluyver takluyver commented on the diff October 15, 2011
IPython/core/interactiveshell.py
... ...
@@ -307,6 +307,9 @@ class InteractiveShell(SingletonConfigurable, Magic):
307 307
         Automatically call the pdb debugger after every exception.
308 308
         """
309 309
     )
  310
+    multiline_history = CBool(False, config=True,
  311
+        help="Store multiple line spanning cells as a single entry in history."
6
Thomas Kluyver Collaborator

This description could probably be clearer. We always store multiline cells in our own history; this affects what happens in readline history - the history you get by pressing up & down arrows, or using Ctrl-R.

Also, I think the config option should be on the TerminalInteractiveShell subclass (IPython.frontends.terminal.interactiveshell), since it doesn't affect other frontends.

Julian Taylor Collaborator

it needs to be in core as there the history is recalled from disk and one must decide to do it line by line or per cell

Thomas Kluyver Collaborator

The code that's initialising readline should now only be run by the terminal frontend (as self.readline_use is now False for the ZMQShell).

In time, we should move the init_readline code to TerminalInteractiveShell as well. In the meantime, just to be on the safe side, we can use getattr(self, 'multiline_history', True), which will behave even if the code does somehow get run by the other frontends.

Min RK Owner
minrk added a note October 16, 2011

Instead of using getattr, let's make multiline_history a non-configurable trait on core.InteractiveShell, with a note that it should be removed when we finish getting the readline code out of the core.

Thomas Kluyver Collaborator

OK, that makes sense. Presumably then we override it with a configurable trait in TerminalInteractiveShell.

Fernando Perez Owner
fperez added a note October 17, 2011

@juliantaylor, does that make sense? It seems that this is almost ready to go, so if you implement @mink's suggestion and make the default true (as there seems to be interest in that), this should be in good shape.

I would, however, like to see a test added. A simple test that pushes two lines of history and gets them back intact should suffice. It must be protected with a decorator so it only runs when readline is present, though.

That will ensure we don't regress on this feature again in the future...

With those changes and a test, I don't see what else would be needed and we can proceed to merge. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Thomas Kluyver
Collaborator

@fperez: Since at least some people feel strongly about this, I think it's worth getting into 0.12. I don't think that's likely to be a problem - it seems to be mostly ready now, it just needs a bit of tweaking and testing.

Everyone: An important question is what the default should be. We changed to readline working line-by-line for 0.11, but a number of people have voiced a preference for the older joined-cell behaviour (see #571). I think I marginally prefer the joined-cell alternative.

Fernando Perez
Owner

It can certainly go into 0.12, I'd be happy to see that. Note that it currently doesn't merge, so unfortunately it's in need of a rebase. @juliantaylor, let us know if you need a hand with that.

@takluyver, as far as the default is concerned, I'm OK if we revert to joined-cell: I honestly think that the readline console is just the wrong environment for multiline work, so I don't really use it that way all that much myself. Hence I don't care too much, and I'm happy to go along with whatever people feel suits them best.

added some commits September 20, 2011
reenable multiline history for terminals
Add configuration variable InteractiveShell.multiline_history.
If it is True cells spanning multiple lines will be saved in history
as a single entry instead of one entry per line, as was the case in
ipython < 0.11.
closes gh-571
30eccbc
ensure history remains intact when recalling multiline entry
Remember previous history length to remove the correct amount
of single line entries
8f24302
clarify variable names 7d6edcb
Fernando Perez
Owner

OK, we don't want this to linger forever, and @takluyver made a good point on IRC: multiline on is really the better default b/c recovering a block that's hidden 20 lines back is a pain otherwise.

I'm making the default be true and merging. Thanks!

Fernando Perez fperez merged commit 7d6edcb into from October 17, 2011
Fernando Perez fperez closed this October 17, 2011
Julian Taylor
Collaborator

sorry, for not reacting for a while, I had some other priorities the last few days.
Thanks for merging.

Min RK minrk referenced this pull request from a commit October 18, 2011
Min RK use unicode_to_str with readline.add_history
mutliline PR #838 added a call to readline.add_history, but without
the unicode/str protection used elsewhere, allowing `In[1]: u'é'` to crash IPython.

This makes the call match others in core.interativeshell.
240f077
Brian E. Granger ellisonbg referenced this pull request from a commit January 10, 2012
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 3 unique commits by 1 author.

Oct 16, 2011
reenable multiline history for terminals
Add configuration variable InteractiveShell.multiline_history.
If it is True cells spanning multiple lines will be saved in history
as a single entry instead of one entry per line, as was the case in
ipython < 0.11.
closes gh-571
30eccbc
ensure history remains intact when recalling multiline entry
Remember previous history length to remove the correct amount
of single line entries
8f24302
Oct 17, 2011
clarify variable names 7d6edcb
This page is out of date. Refresh to see the latest.
13  IPython/core/interactiveshell.py
@@ -307,6 +307,9 @@ def _exiter_default(self):
307 307
         Automatically call the pdb debugger after every exception.
308 308
         """
309 309
     )
  310
+    multiline_history = CBool(False, config=True,
  311
+        help="Store multiple line spanning cells as a single entry in history."
  312
+    )
310 313
 
311 314
     prompt_in1 = Unicode('In [\\#]: ', config=True)
312 315
     prompt_in2 = Unicode('   .\\D.: ', config=True)
@@ -1721,9 +1724,13 @@ def refill_readline_hist(self):
1721 1724
         for _, _, cell in self.history_manager.get_tail(1000,
1722 1725
                                                         include_latest=True):
1723 1726
             if cell.strip(): # Ignore blank lines
1724  
-                for line in cell.splitlines():
1725  
-                    self.readline.add_history(py3compat.unicode_to_str(line,
1726  
-                                                                stdin_encoding))
  1727
+                if self.multiline_history:
  1728
+                      self.readline.add_history(py3compat.unicode_to_str(cell.rstrip(),
  1729
+                                                                         stdin_encoding))
  1730
+                else:
  1731
+                    for line in cell.splitlines():
  1732
+                        self.readline.add_history(py3compat.unicode_to_str(line,
  1733
+                                                                           stdin_encoding))
1727 1734
 
1728 1735
     def set_next_input(self, s):
1729 1736
         """ Sets the 'default' input string for the next command line.
15  IPython/frontend/terminal/interactiveshell.py
@@ -229,6 +229,14 @@ def mainloop(self, display_banner=None):
229 229
                     # handling seems rather unpredictable...
230 230
                     self.write("\nKeyboardInterrupt in interact()\n")
231 231
 
  232
+    def _replace_rlhist_multiline(self, source_raw, hlen_before_cell):
  233
+        """Store multiple lines as a single entry in history"""
  234
+        if self.multiline_history and self.has_readline:
  235
+            hlen = self.readline.get_current_history_length()
  236
+            for i in range(hlen - hlen_before_cell):
  237
+                self.readline.remove_history_item(hlen - i - 1)
  238
+            self.readline.add_history(source_raw.rstrip())
  239
+
232 240
     def interact(self, display_banner=None):
233 241
         """Closely emulate the interactive Python console."""
234 242
 
@@ -245,6 +253,7 @@ def interact(self, display_banner=None):
245 253
             self.show_banner()
246 254
 
247 255
         more = False
  256
+        hlen_before_cell = self.readline.get_current_history_length()
248 257
 
249 258
         # Mark activity in the builtins
250 259
         __builtin__.__dict__['__IPYTHON__active'] += 1
@@ -281,7 +290,9 @@ def interact(self, display_banner=None):
281 290
                 #double-guard against keyboardinterrupts during kbdint handling
282 291
                 try:
283 292
                     self.write('\nKeyboardInterrupt\n')
284  
-                    self.input_splitter.reset()
  293
+                    source_raw = self.input_splitter.source_raw_reset()[1]
  294
+                    self._replace_rlhist_multiline(source_raw, hlen_before_cell)
  295
+                    hlen_before_cell = self.readline.get_current_history_length()
285 296
                     more = False
286 297
                 except KeyboardInterrupt:
287 298
                     pass
@@ -309,6 +320,8 @@ def interact(self, display_banner=None):
309 320
                     self.edit_syntax_error()
310 321
                 if not more:
311 322
                     source_raw = self.input_splitter.source_raw_reset()[1]
  323
+                    self._replace_rlhist_multiline(source_raw, hlen_before_cell)
  324
+                    hlen_before_cell = self.readline.get_current_history_length()
312 325
                     self.run_cell(source_raw, store_history=True)
313 326
 
314 327
         # We are off again...
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.