Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Usability improvements to history in Qt console #356

Merged
merged 2 commits into from

2 participants

@epatters

I have implemented readline-style history saving and optional (off by default) history locking as discussed in #338.

@fperez
I have used Shift as the modifier for overriding the history lock. This is symmetric with the Shift+Enter keybinding, which I like, but it also means that Shift+Up/Down are context-sensitive: they perform text selection if not on the first/last lines. I considered using Ctrl, but then Ctrl+P and Ctrl+N would not be supported properly, which I don't like. That leaves Alt and Meta, but both are more obscure and more difficult to press than Shift or Ctrl. In short, I am paralyzed by indecision :)

@fperez
Owner

This looks awesome, thanks!

When we get proper config support for the console it will be easy for users to set their preference, let's leave it as is for now.

Only one question: is it easy to somehow provide visual indication if the cell is 'locked'? Something like changing the background color or adding a thin line above it to indicate the cursor now has a 'wall'?

If something like that is easy to add, it would be a great addition. If not, go ahead and merge and push.

Many thanks for this! I'll make new issues for any other usability points I find.

@epatters

I do think a faint background color would be a nice way to indicate that the buffer is locked, but since this is fairly non-trivial I'm going to hold off on it for now.

Feel free to create a low-priority ticket for this if you like.

@fperez
Owner

OK, no prob. Then go ahead and merge, I'll make the ticket, prio-low, tagged to you.

@epatters epatters merged commit 66c6673 into ipython:master
@fperez
Owner

Done as #358.

@damianavila damianavila referenced this pull request from a commit
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
Commits on Apr 11, 2011
  1. Add 'history_lock' setting to Qt console.

    epatters authored
    Closes #338
This page is out of date. Refresh to see the latest.
View
8 IPython/frontend/qt/console/console_widget.py
@@ -652,13 +652,13 @@ def _prompt_finished_hook(self):
"""
pass
- def _up_pressed(self):
+ def _up_pressed(self, shift_modifier):
""" Called when the up key is pressed. Returns whether to continue
processing the event.
"""
return True
- def _down_pressed(self):
+ def _down_pressed(self, shift_modifier):
""" Called when the down key is pressed. Returns whether to continue
processing the event.
"""
@@ -1040,14 +1040,14 @@ def _event_filter_console_keypress(self, event):
intercepted = True
elif key == QtCore.Qt.Key_Up:
- if self._reading or not self._up_pressed():
+ if self._reading or not self._up_pressed(shift_down):
intercepted = True
else:
prompt_line = self._get_prompt_cursor().blockNumber()
intercepted = cursor.blockNumber() <= prompt_line
elif key == QtCore.Qt.Key_Down:
- if self._reading or not self._down_pressed():
+ if self._reading or not self._down_pressed(shift_down):
intercepted = True
else:
end_line = self._get_end_cursor().blockNumber()
View
101 IPython/frontend/qt/console/history_console_widget.py
@@ -2,6 +2,7 @@
from IPython.external.qt import QtGui
# Local imports
+from IPython.utils.traitlets import Bool
from console_widget import ConsoleWidget
@@ -9,6 +10,13 @@ class HistoryConsoleWidget(ConsoleWidget):
""" A ConsoleWidget that keeps a history of the commands that have been
executed and provides a readline-esque interface to this history.
"""
+
+ #------ Configuration ------------------------------------------------------
+
+ # If enabled, the input buffer will become "locked" to history movement when
+ # an edit is made to a multi-line input buffer. To override the lock, use
+ # Shift in conjunction with the standard history cycling keys.
+ history_lock = Bool(False, config=True)
#---------------------------------------------------------------------------
# 'object' interface
@@ -19,6 +27,7 @@ def __init__(self, *args, **kw):
# HistoryConsoleWidget protected variables.
self._history = []
+ self._history_edits = {}
self._history_index = 0
self._history_prefix = ''
@@ -42,6 +51,9 @@ def execute(self, source=None, hidden=False, interactive=False):
if history and (not self._history or self._history[-1] != history):
self._history.append(history)
+ # Emulate readline: reset all history edits.
+ self._history_edits = {}
+
# Move the history index to the most recent item.
self._history_index = len(self._history)
@@ -51,12 +63,15 @@ def execute(self, source=None, hidden=False, interactive=False):
# 'ConsoleWidget' abstract interface
#---------------------------------------------------------------------------
- def _up_pressed(self):
+ def _up_pressed(self, shift_modifier):
""" Called when the up key is pressed. Returns whether to continue
processing the event.
"""
prompt_cursor = self._get_prompt_cursor()
if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
+ # Bail out if we're locked.
+ if self._history_locked() and not shift_modifier:
+ return False
# Set a search prefix based on the cursor position.
col = self._get_input_buffer_cursor_column()
@@ -84,21 +99,24 @@ def _up_pressed(self):
return True
- def _down_pressed(self):
+ def _down_pressed(self, shift_modifier):
""" Called when the down key is pressed. Returns whether to continue
processing the event.
"""
end_cursor = self._get_end_cursor()
if self._get_cursor().blockNumber() == end_cursor.blockNumber():
+ # Bail out if we're locked.
+ if self._history_locked() and not shift_modifier:
+ return False
# Perform the search.
- self.history_next(self._history_prefix)
+ replaced = self.history_next(self._history_prefix)
# Emulate readline: keep the cursor position fixed for a prefix
# search. (We don't need to move the cursor to the end of the buffer
# in the other case because this happens automatically when the
# input buffer is set.)
- if self._history_prefix:
+ if self._history_prefix and replaced:
cursor = self._get_prompt_cursor()
cursor.movePosition(QtGui.QTextCursor.Right,
n=len(self._history_prefix))
@@ -113,50 +131,66 @@ def _down_pressed(self):
#---------------------------------------------------------------------------
def history_previous(self, prefix=''):
- """ If possible, set the input buffer to a previous item in the history.
+ """ If possible, set the input buffer to a previous history item.
Parameters:
-----------
prefix : str, optional
If specified, search for an item with this prefix.
+
+ Returns:
+ --------
+ Whether the input buffer was changed.
"""
index = self._history_index
+ replace = False
while index > 0:
index -= 1
- history = self._history[index]
+ history = self._get_edited_history(index)
if history.startswith(prefix):
+ replace = True
break
- else:
- history = None
- if history is not None:
+ if replace:
+ self._store_edits()
self._history_index = index
self.input_buffer = history
+ return replace
+
def history_next(self, prefix=''):
- """ Set the input buffer to a subsequent item in the history, or to the
- original search prefix if there is no such item.
+ """ If possible, set the input buffer to a subsequent history item.
Parameters:
-----------
prefix : str, optional
If specified, search for an item with this prefix.
+
+ Returns:
+ --------
+ Whether the input buffer was changed.
"""
- while self._history_index < len(self._history) - 1:
- self._history_index += 1
- history = self._history[self._history_index]
+ index = self._history_index
+ replace = False
+ while self._history_index < len(self._history):
+ index += 1
+ history = self._get_edited_history(index)
if history.startswith(prefix):
+ replace = True
break
- else:
- self._history_index = len(self._history)
- history = prefix
- self.input_buffer = history
+
+ if replace:
+ self._store_edits()
+ self._history_index = index
+ self.input_buffer = history
+
+ return replace
def history_tail(self, n=10):
""" Get the local history list.
- Parameters
- ----------
+ Parameters:
+ -----------
n : int
The (maximum) number of history items to get.
"""
@@ -166,8 +200,35 @@ def history_tail(self, n=10):
# 'HistoryConsoleWidget' protected interface
#---------------------------------------------------------------------------
+ def _history_locked(self):
+ """ Returns whether history movement is locked.
+ """
+ return (self.history_lock and
+ (self._get_edited_history(self._history_index) !=
+ self.input_buffer) and
+ (self._get_prompt_cursor().blockNumber() !=
+ self._get_end_cursor().blockNumber()))
+
+ def _get_edited_history(self, index):
+ """ Retrieves a history item, possibly with temporary edits.
+ """
+ if index in self._history_edits:
+ return self._history_edits[index]
+ elif index == len(self._history):
+ return unicode()
+ return self._history[index]
+
def _set_history(self, history):
""" Replace the current history with a sequence of history items.
"""
self._history = list(history)
+ self._history_edits = {}
self._history_index = len(self._history)
+
+ def _store_edits(self):
+ """ If there are edits to the current input buffer, store them.
+ """
+ current = self.input_buffer
+ if self._history_index == len(self._history) or \
+ self._history[self._history_index] != current:
+ self._history_edits[self._history_index] = current
Something went wrong with that request. Please try again.