history_console_widget.py
304 lines
| 11.4 KiB
| text/x-python
|
PythonLexer
epatters
|
r2983 | # System library imports | ||
Evan Patterson
|
r3304 | from IPython.external.qt import QtGui | ||
epatters
|
r2983 | |||
# Local imports | ||||
epatters
|
r3728 | from IPython.utils.traitlets import Bool | ||
Thomas Kluyver
|
r13347 | from .console_widget import ConsoleWidget | ||
epatters
|
r2983 | |||
class HistoryConsoleWidget(ConsoleWidget): | ||||
""" A ConsoleWidget that keeps a history of the commands that have been | ||||
epatters
|
r2984 | executed and provides a readline-esque interface to this history. | ||
epatters
|
r2983 | """ | ||
epatters
|
r3728 | |||
#------ 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) | ||||
Bernardo B. Marques
|
r4872 | |||
epatters
|
r2983 | #--------------------------------------------------------------------------- | ||
# 'object' interface | ||||
#--------------------------------------------------------------------------- | ||||
def __init__(self, *args, **kw): | ||||
super(HistoryConsoleWidget, self).__init__(*args, **kw) | ||||
epatters
|
r2984 | |||
# HistoryConsoleWidget protected variables. | ||||
epatters
|
r2983 | self._history = [] | ||
epatters
|
r3727 | self._history_edits = {} | ||
epatters
|
r2983 | self._history_index = 0 | ||
epatters
|
r2984 | self._history_prefix = '' | ||
epatters
|
r2983 | |||
#--------------------------------------------------------------------------- | ||||
# 'ConsoleWidget' public interface | ||||
#--------------------------------------------------------------------------- | ||||
def execute(self, source=None, hidden=False, interactive=False): | ||||
""" Reimplemented to the store history. | ||||
""" | ||||
if not hidden: | ||||
history = self.input_buffer if source is None else source | ||||
executed = super(HistoryConsoleWidget, self).execute( | ||||
source, hidden, interactive) | ||||
if executed and not hidden: | ||||
Bernardo B. Marques
|
r4872 | # Save the command unless it was an empty string or was identical | ||
epatters
|
r2983 | # to the previous command. | ||
history = history.rstrip() | ||||
if history and (not self._history or self._history[-1] != history): | ||||
self._history.append(history) | ||||
epatters
|
r3727 | # Emulate readline: reset all history edits. | ||
self._history_edits = {} | ||||
epatters
|
r2983 | # Move the history index to the most recent item. | ||
self._history_index = len(self._history) | ||||
return executed | ||||
#--------------------------------------------------------------------------- | ||||
# 'ConsoleWidget' abstract interface | ||||
#--------------------------------------------------------------------------- | ||||
epatters
|
r3728 | def _up_pressed(self, shift_modifier): | ||
epatters
|
r2983 | """ 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(): | ||||
epatters
|
r3728 | # Bail out if we're locked. | ||
if self._history_locked() and not shift_modifier: | ||||
return False | ||||
epatters
|
r2983 | |||
epatters
|
r2984 | # Set a search prefix based on the cursor position. | ||
col = self._get_input_buffer_cursor_column() | ||||
input_buffer = self.input_buffer | ||||
MinRK
|
r9205 | # use the *shortest* of the cursor column and the history prefix | ||
# to determine if the prefix has changed | ||||
n = min(col, len(self._history_prefix)) | ||||
# prefix changed, restart search from the beginning | ||||
if (self._history_prefix[:n] != input_buffer[:n]): | ||||
epatters
|
r2984 | self._history_index = len(self._history) | ||
MinRK
|
r9205 | |||
# the only time we shouldn't set the history prefix | ||||
# to the line up to the cursor is if we are already | ||||
# in a simple scroll (no prefix), | ||||
# and the cursor is at the end of the first line | ||||
MinRK
|
r9704 | |||
# check if we are at the end of the first line | ||||
MinRK
|
r9705 | c = self._get_cursor() | ||
MinRK
|
r9704 | current_pos = c.position() | ||
c.movePosition(QtGui.QTextCursor.EndOfLine) | ||||
at_eol = (c.position() == current_pos) | ||||
MinRK
|
r9205 | if self._history_index == len(self._history) or \ | ||
MinRK
|
r9704 | not (self._history_prefix == '' and at_eol) or \ | ||
MinRK
|
r9434 | not (self._get_edited_history(self._history_index)[:col] == input_buffer[:col]): | ||
epatters
|
r2984 | self._history_prefix = input_buffer[:col] | ||
# Perform the search. | ||||
Benjamin Thyreau
|
r4481 | self.history_previous(self._history_prefix, | ||
as_prefix=not shift_modifier) | ||||
epatters
|
r2984 | |||
# Go to the first line of the prompt for seemless history scrolling. | ||||
# Emulate readline: keep the cursor position fixed for a prefix | ||||
# search. | ||||
epatters
|
r2983 | cursor = self._get_prompt_cursor() | ||
epatters
|
r2984 | if self._history_prefix: | ||
Bernardo B. Marques
|
r4872 | cursor.movePosition(QtGui.QTextCursor.Right, | ||
epatters
|
r2984 | n=len(self._history_prefix)) | ||
else: | ||||
cursor.movePosition(QtGui.QTextCursor.EndOfLine) | ||||
epatters
|
r2983 | self._set_cursor(cursor) | ||
return False | ||||
epatters
|
r2984 | |||
epatters
|
r2983 | return True | ||
epatters
|
r3728 | def _down_pressed(self, shift_modifier): | ||
epatters
|
r2983 | """ 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(): | ||||
epatters
|
r3728 | # Bail out if we're locked. | ||
if self._history_locked() and not shift_modifier: | ||||
return False | ||||
epatters
|
r2984 | |||
# Perform the search. | ||||
Bernardo B. Marques
|
r4872 | replaced = self.history_next(self._history_prefix, | ||
Benjamin Thyreau
|
r4481 | as_prefix=not shift_modifier) | ||
epatters
|
r2984 | |||
# 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.) | ||||
epatters
|
r3728 | if self._history_prefix and replaced: | ||
epatters
|
r2984 | cursor = self._get_prompt_cursor() | ||
Bernardo B. Marques
|
r4872 | cursor.movePosition(QtGui.QTextCursor.Right, | ||
epatters
|
r2984 | n=len(self._history_prefix)) | ||
self._set_cursor(cursor) | ||||
epatters
|
r2983 | return False | ||
epatters
|
r2984 | |||
epatters
|
r2983 | return True | ||
#--------------------------------------------------------------------------- | ||||
# 'HistoryConsoleWidget' public interface | ||||
#--------------------------------------------------------------------------- | ||||
Benjamin Thyreau
|
r4481 | def history_previous(self, substring='', as_prefix=True): | ||
epatters
|
r3727 | """ If possible, set the input buffer to a previous history item. | ||
epatters
|
r2983 | |||
epatters
|
r2984 | Parameters: | ||
----------- | ||||
Benjamin Thyreau
|
r4481 | substring : str, optional | ||
If specified, search for an item with this substring. | ||||
as_prefix : bool, optional | ||||
If True, the substring must match at the beginning (default). | ||||
epatters
|
r3728 | |||
Returns: | ||||
-------- | ||||
Whether the input buffer was changed. | ||||
epatters
|
r2983 | """ | ||
epatters
|
r2984 | index = self._history_index | ||
epatters
|
r3728 | replace = False | ||
epatters
|
r2984 | while index > 0: | ||
index -= 1 | ||||
epatters
|
r3727 | history = self._get_edited_history(index) | ||
Benjamin Thyreau
|
r4481 | if (as_prefix and history.startswith(substring)) \ | ||
or (not as_prefix and substring in history): | ||||
epatters
|
r3728 | replace = True | ||
epatters
|
r2984 | break | ||
Bernardo B. Marques
|
r4872 | |||
epatters
|
r3728 | if replace: | ||
self._store_edits() | ||||
epatters
|
r2984 | self._history_index = index | ||
epatters
|
r3728 | self.input_buffer = history | ||
return replace | ||||
epatters
|
r2984 | |||
Benjamin Thyreau
|
r4481 | def history_next(self, substring='', as_prefix=True): | ||
epatters
|
r3727 | """ If possible, set the input buffer to a subsequent history item. | ||
epatters
|
r2984 | |||
Parameters: | ||||
----------- | ||||
Benjamin Thyreau
|
r4481 | substring : str, optional | ||
If specified, search for an item with this substring. | ||||
as_prefix : bool, optional | ||||
If True, the substring must match at the beginning (default). | ||||
epatters
|
r3728 | |||
Returns: | ||||
-------- | ||||
Whether the input buffer was changed. | ||||
epatters
|
r2984 | """ | ||
epatters
|
r3727 | index = self._history_index | ||
epatters
|
r3728 | replace = False | ||
MinRK
|
r9206 | while index < len(self._history): | ||
epatters
|
r3727 | index += 1 | ||
history = self._get_edited_history(index) | ||||
Benjamin Thyreau
|
r4481 | if (as_prefix and history.startswith(substring)) \ | ||
or (not as_prefix and substring in history): | ||||
epatters
|
r3728 | replace = True | ||
epatters
|
r2984 | break | ||
Bernardo B. Marques
|
r4872 | |||
epatters
|
r3728 | if replace: | ||
self._store_edits() | ||||
epatters
|
r3727 | self._history_index = index | ||
epatters
|
r3728 | self.input_buffer = history | ||
return replace | ||||
epatters
|
r2983 | |||
epatters
|
r3516 | def history_tail(self, n=10): | ||
""" Get the local history list. | ||||
epatters
|
r3727 | Parameters: | ||
----------- | ||||
epatters
|
r3516 | n : int | ||
The (maximum) number of history items to get. | ||||
""" | ||||
return self._history[-n:] | ||||
Bernardo B. Marques
|
r4872 | |||
Matthias BUSSONNIER
|
r5030 | def _request_update_session_history_length(self): | ||
MinRK
|
r10288 | msg_id = self.kernel_client.shell_channel.execute('', | ||
Matthias BUSSONNIER
|
r5030 | silent=True, | ||
user_expressions={ | ||||
'hlen':'len(get_ipython().history_manager.input_hist_raw)', | ||||
} | ||||
) | ||||
Matthias BUSSONNIER
|
r5506 | self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic') | ||
Matthias BUSSONNIER
|
r5030 | |||
def _handle_execute_reply(self, msg): | ||||
""" Handles replies for code execution, here only session history length | ||||
""" | ||||
Matthias BUSSONNIER
|
r5506 | msg_id = msg['parent_header']['msg_id'] | ||
Puneeth Chaganti
|
r5846 | info = self._request_info['execute'].pop(msg_id,None) | ||
Matthias BUSSONNIER
|
r5520 | if info and info.kind == 'save_magic' and not self._hidden: | ||
content = msg['content'] | ||||
status = content['status'] | ||||
if status == 'ok': | ||||
MinRK
|
r10681 | self._max_session_history = int( | ||
content['user_expressions']['hlen']['data']['text/plain'] | ||||
) | ||||
Matthias BUSSONNIER
|
r5030 | |||
Matthias BUSSONNIER
|
r5027 | def save_magic(self): | ||
Matthias BUSSONNIER
|
r5030 | # update the session history length | ||
self._request_update_session_history_length() | ||||
file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self, | ||||
"Enter A filename", | ||||
filter='Python File (*.py);; All files (*.*)' | ||||
) | ||||
# let's the user search/type for a file name, while the history length | ||||
# is fetched | ||||
if file_name: | ||||
Matthias BUSSONNIER
|
r5027 | hist_range, ok = QtGui.QInputDialog.getText(self, | ||
'Please enter an interval of command to save', | ||||
'Saving commands:', | ||||
Matthias BUSSONNIER
|
r5030 | text=str('1-'+str(self._max_session_history)) | ||
) | ||||
Matthias BUSSONNIER
|
r5027 | if ok: | ||
Matthias BUSSONNIER
|
r5046 | self.execute("%save"+" "+file_name+" "+str(hist_range)) | ||
Matthias BUSSONNIER
|
r5027 | |||
epatters
|
r2985 | #--------------------------------------------------------------------------- | ||
# 'HistoryConsoleWidget' protected interface | ||||
#--------------------------------------------------------------------------- | ||||
epatters
|
r3728 | def _history_locked(self): | ||
""" Returns whether history movement is locked. | ||||
""" | ||||
Bernardo B. Marques
|
r4872 | return (self.history_lock and | ||
(self._get_edited_history(self._history_index) != | ||||
epatters
|
r3728 | self.input_buffer) and | ||
(self._get_prompt_cursor().blockNumber() != | ||||
self._get_end_cursor().blockNumber())) | ||||
epatters
|
r3727 | 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] | ||||
epatters
|
r3728 | elif index == len(self._history): | ||
return unicode() | ||||
epatters
|
r3727 | return self._history[index] | ||
epatters
|
r2985 | def _set_history(self, history): | ||
epatters
|
r2983 | """ Replace the current history with a sequence of history items. | ||
""" | ||||
self._history = list(history) | ||||
epatters
|
r3727 | self._history_edits = {} | ||
epatters
|
r2983 | self._history_index = len(self._history) | ||
epatters
|
r3728 | |||
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 | ||||