Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Load history from IPython directly #7141

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion spyder/app/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1331,7 +1331,9 @@ def post_visible_setup(self):

# Show history file if no console is visible
if not self.ipyconsole.isvisible:
self.historylog.add_history(get_conf_path('history.py'))
self.historylog.add_history(
self.ipyconsole.history_title,
self.ipyconsole.get_current_shellwidget()._history)

if self.open_project:
self.projects.open_project(self.open_project)
Expand Down
32 changes: 32 additions & 0 deletions spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1792,6 +1792,38 @@ def test_render_issue():
assert test_description in test_issue_2
assert test_traceback in test_issue_2

@flaky(max_runs=3)
@pytest.mark.slow
def test_history(main_window, qtbot):
"""Test History"""
# ---- Setup ----
# Create 3 clients and wait until the window is fully up
for i in range(3):
main_window.ipyconsole.create_new_client()

clients = main_window.ipyconsole.get_clients()
for client in clients:
shell = client.shellwidget
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

#Execute some code in each widget
commands = []
for i, client in enumerate(clients):
shell = client.shellwidget
commands.append('i = {i}\n'.format(i=i))
shell.execute(commands[-1])
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
print(main_window.historylog.editors[0].toPlainText()[-10:])

commands_text = ''.join(commands)

index = main_window.historylog.filenames.index(main_window.ipyconsole.history_title)
assert index == 0
text = main_window.historylog.editors[index].toPlainText()[-len(commands_text):]
history = main_window.historylog.histories[index][-len(clients):]

assert history == commands
assert text == commands_text

@pytest.mark.slow
@flaky(max_runs=3)
Expand Down
2 changes: 1 addition & 1 deletion spyder/config/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ def running_in_mac_app():
#==============================================================================
SAVED_CONFIG_FILES = ('help', 'onlinehelp', 'path', 'pylint.results',
'spyder.ini', 'temp.py', 'temp.spydata', 'template.py',
'history.py', 'history_internal.py', 'workingdir',
'history_internal.py', 'workingdir',
'.projects', '.spyproject', '.ropeproject',
'monitor.log', 'monitor_debug.log', 'rope.log',
'langconfig', 'spyder.lock')
Expand Down
6 changes: 0 additions & 6 deletions spyder/plugins/console/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,6 @@ def __init__(self, parent=None, namespace=None, commands=[], message=None,
self.dismiss_error = False

#------ Private API --------------------------------------------------------
def set_historylog(self, historylog):
"""Bind historylog instance to this console
Not used anymore since v2.0"""
historylog.add_history(self.shell.history_filename)
self.shell.append_to_history.connect(historylog.append_to_history)

def set_help(self, help_plugin):
"""Bind help instance to this console"""
self.shell.help = help_plugin
Expand Down
58 changes: 28 additions & 30 deletions spyder/plugins/history/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def __init__(self, parent):

self.editors = []
self.filenames = []
self.histories = []

# Initialize plugin actions, toolbutton and general signals
self.initialize_plugin()
Expand Down Expand Up @@ -89,14 +90,14 @@ def get_plugin_title(self):
def get_plugin_icon(self):
"""Return widget icon."""
return ima.icon('history')

def get_focus_widget(self):
"""
Return the widget to give focus to when
this plugin's dockwidget is raised on top-level
"""
return self.tabwidget.currentWidget()

def closing_plugin(self, cancelable=False):
"""Perform actions before parent main window is closed"""
return True
Expand Down Expand Up @@ -129,12 +130,11 @@ def get_plugin_actions(self):
def on_first_registration(self):
"""Action to be performed on first plugin registration"""
self.main.tabify_plugins(self.main.ipyconsole, self)

def register_plugin(self):
"""Register plugin in Spyder's main window"""
self.focus_changed.connect(self.main.plugin_focus_changed)
self.main.add_dockwidget(self)
# self.main.console.set_historylog(self)
self.main.console.shell.refresh.connect(self.refresh_plugin)

def update_font(self):
Expand Down Expand Up @@ -178,48 +178,29 @@ def move_tab(self, index_from, index_to):
self.editors.insert(index_to, editor)

#------ Public API ---------------------------------------------------------
def add_history(self, filename):
def add_history(self, filename, history_list):
"""
Add new history tab
Slot for add_history signal emitted by shell instance
"""
filename = encoding.to_unicode_from_fs(filename)
if filename in self.filenames:
return
#Limit history depth
history_list = history_list[-self.get_option('max_entries'):]
editor = codeeditor.CodeEditor(self)
if osp.splitext(filename)[1] == '.py':
language = 'py'
else:
language = 'bat'
editor.setup_editor(linenumbers=self.get_option('line_numbers'),
language=language,
language='py',
scrollflagarea=False)
editor.focus_changed.connect(lambda: self.focus_changed.emit())
editor.setReadOnly(True)
color_scheme = self.get_color_scheme()
editor.set_font( self.get_plugin_font(), color_scheme )
editor.toggle_wrap_mode( self.get_option('wrap') )

# Avoid a possible error when reading the history file
try:
text, _ = encoding.read(filename)
except (IOError, OSError):
text = "# Previous history could not be read from disk, sorry\n\n"
text = normalize_eols(text)
linebreaks = [m.start() for m in re.finditer('\n', text)]
maxNline = self.get_option('max_entries')
if len(linebreaks) > maxNline:
text = text[linebreaks[-maxNline - 1] + 1:]
# Avoid an error when trying to write the trimmed text to
# disk.
# See issue 9093
try:
encoding.write(text, filename)
except (IOError, OSError):
pass
editor.set_text(text)
editor.set_text('\n'.join(history_list) + '\n')
editor.set_cursor_position('eof')

self.histories.append(history_list)
self.editors.append(editor)
self.filenames.append(filename)
index = self.tabwidget.addTab(editor, osp.basename(filename))
Expand All @@ -237,11 +218,28 @@ def append_to_history(self, filename, command):
filename = to_text_string(filename.toUtf8(), 'utf-8')
command = to_text_string(command)
index = self.filenames.index(filename)
self.histories[index].append(command)
self.editors[index].append(command)
if self.get_option('go_to_eof'):
self.editors[index].set_cursor_position('eof')
self.tabwidget.setCurrentIndex(index)


def set_history(self, filename, history_list):
"""
Update the history for history filename
Slot for set_history signal emitted by shell instance
"""
#Limit history depth
history_list = history_list[-self.get_option('max_entries'):]
if not is_text_string(filename): # filename is a QString
filename = to_text_string(filename.toUtf8(), 'utf-8')
index = self.filenames.index(filename)
self.histories[index] = history_list
self.editors[index].set_text('\n'.join(history_list) + '\n')
if self.get_option('go_to_eof'):
self.editors[index].set_cursor_position('eof')
self.tabwidget.setCurrentIndex(index)

@Slot()
def change_history_depth(self):
"Change history max entries"""
Expand Down
31 changes: 17 additions & 14 deletions spyder/plugins/history/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,13 @@ def historylog_with_tab(historylog, mocker, monkeypatch):
monkeypatch.setattr(history.HistoryLog, 'get_option', get_option)
monkeypatch.setattr(history.HistoryLog, 'set_option', set_option)

commands = []
# Create tab for page.
hl.set_option('wrap', False)
hl.set_option('line_numbers', False)
hl.set_option('max_entries', 100)
hl.set_option('go_to_eof', True)
hl.add_history('test_history.py')
hl.add_history('test_history.py', commands)
return hl

#==============================================================================
Expand Down Expand Up @@ -105,6 +106,7 @@ def test_init(historylog):
hl = historylog
assert hl.editors == []
assert hl.filenames == []
assert hl.histories == []
assert len(hl.plugin_actions) == 5
assert len(hl.tabwidget.menu.actions()) == 5
assert len(hl.tabwidget.cornerWidget().menu().actions()) == 5
Expand All @@ -130,11 +132,11 @@ def test_add_history(historylog, mocker, monkeypatch):

# Add one file.
tab1 = 'test_history.py'
text1 = 'a = 5\nb= 10\na + b\n'
commands = ["This", "is", "a", "list", "of", "commands"]

hl.set_option('line_numbers', False)
hl.set_option('wrap', False)
history.encoding.read.return_value = (text1, '')
hl.add_history(tab1)
hl.add_history(tab1, commands)
# Check tab and editor were created correctly.
assert len(hle) == 1
assert hl.filenames == [tab1]
Expand All @@ -143,20 +145,19 @@ def test_add_history(historylog, mocker, monkeypatch):
assert hle[0].wordWrapMode() == QTextOption.NoWrap
assert hl.tabwidget.tabText(0) == tab1
assert hl.tabwidget.tabToolTip(0) == tab1
assert hl.histories[0] == commands

hl.set_option('line_numbers', True)
hl.set_option('wrap', True)
# Try to add same file -- does not process filename again, so
# linenumbers and wrap doesn't change.
hl.add_history(tab1)
hl.add_history(tab1, commands)
assert hl.tabwidget.currentIndex() == 0
assert not hl.editors[0].linenumberarea.isVisible()

# Add another file.
tab2 = 'history2.js'
text2 = 'random text\nspam line\n\n\n\n'
history.encoding.read.return_value = (text2, '')
hl.add_history(tab2)
hl.add_history(tab2, commands)
# Check second tab and editor were created correctly.
assert len(hle) == 2
assert hl.filenames == [tab1, tab2]
Expand All @@ -165,6 +166,7 @@ def test_add_history(historylog, mocker, monkeypatch):
assert hle[1].wordWrapMode() == QTextOption.WrapAtWordBoundaryOrAnywhere
assert hl.tabwidget.tabText(1) == tab2
assert hl.tabwidget.tabToolTip(1) == tab2
assert hl.histories[1] == commands

assert hl.filenames == [tab1, tab2]

Expand All @@ -173,13 +175,14 @@ def test_add_history(historylog, mocker, monkeypatch):
assert hle[0].is_python()
assert hle[0].isReadOnly()
assert not hle[0].isVisible()
assert hle[0].toPlainText() == text1
assert hle[0].toPlainText() == '\n'.join(commands) + '\n'

assert not hle[1].supported_language
assert not hle[1].is_python()
# The history is loaded from iPython so it has to be python
# assert not hle[1].supported_language
# assert not hle[1].is_python()
assert hle[1].isReadOnly()
assert hle[1].isVisible()
assert hle[1].toPlainText() == text2
assert hle[1].toPlainText() == '\n'.join(commands) + '\n'


def test_append_to_history(historylog_with_tab, mocker):
Expand All @@ -195,7 +198,7 @@ def test_append_to_history(historylog_with_tab, mocker):
# Force cursor to the beginning of the file.
hl.editors[0].set_cursor_position('sof')
hl.append_to_history('test_history.py', 'import re\n')
assert hl.editors[0].toPlainText() == 'import re\n'
assert hl.editors[0].toPlainText() == '\nimport re\n'
assert hl.tabwidget.currentIndex() == 0
# Cursor moved to end.
assert hl.editors[0].is_cursor_at_end()
Expand All @@ -206,7 +209,7 @@ def test_append_to_history(historylog_with_tab, mocker):
# Force cursor to the beginning of the file.
hl.editors[0].set_cursor_position('sof')
hl.append_to_history('test_history.py', 'a = r"[a-z]"\n')
assert hl.editors[0].toPlainText() == 'import re\na = r"[a-z]"\n'
assert hl.editors[0].toPlainText() == '\nimport re\na = r"[a-z]"\n'
# Cursor not at end.
assert not hl.editors[0].is_cursor_at_end()

Expand Down
18 changes: 13 additions & 5 deletions spyder/plugins/ipythonconsole/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def __init__(self, parent, testing=False, test_dir=None,

self.tabwidget = None
self.menu_actions = None
self.history_title = "History"
self.master_clients = 0
self.clients = []
self.filenames = []
Expand Down Expand Up @@ -642,7 +643,6 @@ def create_new_client(self, give_focus=True, filename='', is_cython=False,
reset_warning = self.get_option('show_reset_namespace_warning')
ask_before_restart = self.get_option('ask_before_restart')
client = ClientWidget(self, id_=client_id,
history_filename=get_conf_path('history.py'),
config_options=self.config_options(),
additional_options=self.additional_options(
is_pylab=is_pylab,
Expand Down Expand Up @@ -917,9 +917,18 @@ def register_client(self, client, give_focus=True):

# Connect client to our history log
if self.main.historylog is not None:
self.main.historylog.add_history(client.history_filename)
client.append_to_history.connect(
self.main.historylog.append_to_history)
self.main.historylog.add_history(
self.history_title,
shellwidget.history_tail(
self.main.historylog.get_option('max_entries')))
# To save history
shellwidget.sig_new_history.connect(
lambda history: self.main.historylog.set_history(
self.history_title, history))
shellwidget.executing.connect(
lambda command: self.main.historylog.append_to_history(
self.history_title, command))


# Set font for client
client.set_font( self.get_plugin_font() )
Expand Down Expand Up @@ -1442,7 +1451,6 @@ def _create_client_for_kernel(self, connection_file, hostname, sshkey,
client = ClientWidget(self,
id_=client_id,
given_name=given_name,
history_filename=get_conf_path('history.py'),
config_options=self.config_options(),
additional_options=self.additional_options(),
interpreter_versions=self.interpreter_versions(),
Expand Down
Loading