Skip to content

Commit

Permalink
Merge remote-tracking branch 'spyder-ide/3.x' into Spyder--Fix-to-issue-
Browse files Browse the repository at this point in the history
  • Loading branch information
Ericvulpi committed Jul 8, 2017
2 parents 7665e59 + bf64ee5 commit bd99672
Show file tree
Hide file tree
Showing 33 changed files with 843 additions and 3,647 deletions.
9 changes: 5 additions & 4 deletions spyder/app/mainwindow.py
Expand Up @@ -432,7 +432,7 @@ def signal_handler(signum, frame=None):
if options.window_title is not None:
title += ' -- ' + options.window_title

if DEV or DEBUG or PYTEST:
if DEBUG or PYTEST:
# Show errors in internal console when developing or testing.
CONF.set('main', 'show_internal_console_if_traceback', True)

Expand Down Expand Up @@ -2107,7 +2107,8 @@ def set_splash(self, message):

def remove_tmpdir(self):
"""Remove Spyder temporary directory"""
shutil.rmtree(programs.TEMPDIR, ignore_errors=True)
if CONF.get('main', 'single_instance') and not self.new_instance:
shutil.rmtree(programs.TEMPDIR, ignore_errors=True)

def closeEvent(self, event):
"""closeEvent reimplementation"""
Expand Down Expand Up @@ -2433,8 +2434,8 @@ def open_external_console(self, fname, wdir, args, interact, debug, python,

def execute_in_external_console(self, lines, focus_to_editor):
"""
Execute lines in external or IPython console and eventually set focus
to the editor
Execute lines in IPython console and eventually set focus
to the Editor.
"""
console = self.ipyconsole
console.visibility_changed(True)
Expand Down
182 changes: 149 additions & 33 deletions spyder/app/tests/test_mainwindow.py
Expand Up @@ -14,6 +14,7 @@
import tempfile

from flaky import flaky
from jupyter_client.manager import KernelManager
import numpy as np
from numpy.testing import assert_array_equal
import pytest
Expand All @@ -25,6 +26,7 @@
from spyder.app.cli_options import get_options
from spyder.app.mainwindow import initialize, run_spyder
from spyder.py3compat import PY2
from spyder.utils.ipython.kernelspec import SpyderKernelSpec
from spyder.utils.programs import is_module_installed
from spyder.utils.test import close_save_message_box

Expand All @@ -49,6 +51,8 @@
# Test for PyQt 5 wheels
PYQT_WHEEL = PYQT_VERSION > '5.6'

# Temporary directory
TEMP_DIRECTORY = tempfile.gettempdir()

#==============================================================================
# Utility functions
Expand All @@ -74,6 +78,25 @@ def reset_run_code(qtbot, shell, code_editor, nsb):
qtbot.keyClick(code_editor, Qt.Key_Home, modifier=Qt.ControlModifier)


def start_new_kernel(startup_timeout=60, kernel_name='python', spykernel=False,
**kwargs):
"""Start a new kernel, and return its Manager and Client"""
km = KernelManager(kernel_name=kernel_name)
if spykernel:
km._kernel_spec = SpyderKernelSpec()
km.start_kernel(**kwargs)
kc = km.client()
kc.start_channels()
try:
kc.wait_for_ready(timeout=startup_timeout)
except RuntimeError:
kc.stop_channels()
km.shutdown_kernel()
raise

return km, kc


#==============================================================================
# Fixtures
#==============================================================================
Expand Down Expand Up @@ -104,6 +127,80 @@ def close_window():
#==============================================================================
# Tests
#==============================================================================
# IMPORTANT NOTE: Please leave this test to be the first one here to
# avoid possible timeouts in Appveyor
@flaky(max_runs=3)
@pytest.mark.skipif(os.name != 'nt' or not PY2,
reason="It times out on Linux and Python 3")
@pytest.mark.timeout(timeout=60, method='thread')
@pytest.mark.use_introspection
def test_calltip(main_window, qtbot):
"""Hide the calltip in the editor when a matching ')' is found."""
# Load test file
text = 'a = [1,2,3]\n(max'
main_window.editor.new(fname="test.py", text=text)
code_editor = main_window.editor.get_focus_widget()

# Set text to start
code_editor.set_text(text)
code_editor.go_to_line(2)
code_editor.move_cursor(5)
calltip = code_editor.calltip_widget
assert not calltip.isVisible()

qtbot.keyPress(code_editor, Qt.Key_ParenLeft, delay=3000)
qtbot.keyPress(code_editor, Qt.Key_A, delay=1000)
qtbot.waitUntil(lambda: calltip.isVisible(), timeout=1000)

qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
qtbot.keyPress(code_editor, Qt.Key_Space)
assert not calltip.isVisible()
qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
qtbot.keyPress(code_editor, Qt.Key_Enter, delay=1000)

QTimer.singleShot(1000, lambda: close_save_message_box(qtbot))
main_window.editor.close_file()


@flaky(max_runs=3)
def test_connection_to_external_kernel(main_window, qtbot):
"""Test that only Spyder kernels are connected to the Variable Explorer."""
# Test with a generic kernel
km, kc = start_new_kernel()

main_window.ipyconsole._create_client_for_kernel(kc.connection_file, None,
None, None)
shell = main_window.ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
with qtbot.waitSignal(shell.executed):
shell.execute('a = 10')

# Assert that there are no variables in the variable explorer
main_window.variableexplorer.visibility_changed(True)
nsb = main_window.variableexplorer.get_focus_widget()
qtbot.wait(500)
assert nsb.editor.model.rowCount() == 0

# Test with a kernel from Spyder
spykm, spykc = start_new_kernel(spykernel=True)
main_window.ipyconsole._create_client_for_kernel(spykc.connection_file, None,
None, None)
shell = main_window.ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
with qtbot.waitSignal(shell.executed):
shell.execute('a = 10')

# Assert that a variable is visible in the variable explorer
main_window.variableexplorer.visibility_changed(True)
nsb = main_window.variableexplorer.get_focus_widget()
qtbot.wait(500)
assert nsb.editor.model.rowCount() == 1

# Shutdown the kernels
spykm.shutdown_kernel(now=True)
km.shutdown_kernel(now=True)


@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
def test_np_threshold(main_window, qtbot):
Expand Down Expand Up @@ -156,39 +253,6 @@ def test_change_types_in_varexp(main_window, qtbot):
assert shell.get_value('a') == 10


@flaky(max_runs=3)
@pytest.mark.skipif(os.name != 'nt' or not PY2,
reason="It times out on Linux and Python 3")
@pytest.mark.timeout(timeout=60, method='thread')
@pytest.mark.use_introspection
def test_calltip(main_window, qtbot):
"""Hide the calltip in the editor when a matching ')' is found."""
# Load test file
text = 'a = [1,2,3]\n(max'
main_window.editor.new(fname="test.py", text=text)
code_editor = main_window.editor.get_focus_widget()

# Set text to start
code_editor.set_text(text)
code_editor.go_to_line(2)
code_editor.move_cursor(5)
calltip = code_editor.calltip_widget
assert not calltip.isVisible()

qtbot.keyPress(code_editor, Qt.Key_ParenLeft, delay=3000)
qtbot.keyPress(code_editor, Qt.Key_A, delay=1000)
qtbot.waitUntil(lambda: calltip.isVisible(), timeout=1000)

qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
qtbot.keyPress(code_editor, Qt.Key_Space)
assert not calltip.isVisible()
qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
qtbot.keyPress(code_editor, Qt.Key_Enter, delay=1000)

QTimer.singleShot(1000, lambda: close_save_message_box(qtbot))
main_window.editor.close_file()


@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt' or not is_module_installed('Cython'),
reason="It times out sometimes on Windows and Cython is needed")
Expand Down Expand Up @@ -706,5 +770,57 @@ def test_varexp_magic_dbg(main_window, qtbot):
assert shell._control.toHtml().count('img src') == 1


@flaky(max_runs=3)
def test_fileswitcher(main_window, qtbot):
"""Test the use of shorten paths when necessary in the fileswitcher."""
# Load tests files
dir_b = osp.join(TEMP_DIRECTORY, 'temp_dir_a', 'temp_b')
filename_b = osp.join(dir_b, 'c.py')
if not osp.isdir(dir_b):
os.makedirs(dir_b)
if not osp.isfile(filename_b):
file_c = open(filename_b, 'w+')
file_c.close()
if PYQT5:
dir_d = osp.join(TEMP_DIRECTORY, 'temp_dir_a', 'temp_c', 'temp_d', 'temp_e')
else:
dir_d = osp.join(TEMP_DIRECTORY, 'temp_dir_a', 'temp_c', 'temp_d')
dir_e = osp.join(TEMP_DIRECTORY, 'temp_dir_a', 'temp_c', 'temp_dir_f', 'temp_e')
filename_e = osp.join(dir_e, 'a.py')
if not osp.isdir(dir_e):
os.makedirs(dir_e)
if not osp.isfile(filename_e):
file_e = open(filename_e, 'w+')
file_e.close()
filename_d = osp.join(dir_d, 'c.py')
if not osp.isdir(dir_d):
os.makedirs(dir_d)
if not osp.isfile(filename_d):
file_d = open(filename_d, 'w+')
file_d.close()
main_window.editor.load(filename_b)
main_window.editor.load(filename_d)

# Assert that all the path of the file is shown
main_window.open_fileswitcher()
if os.name == 'nt':
item_text = main_window.fileswitcher.list.currentItem().text().replace('\\', '/').lower()
dir_d = dir_d.replace('\\', '/').lower()
else:
item_text = main_window.fileswitcher.list.currentItem().text()
assert dir_d in item_text

# Resize Main Window to a third of its width
size = main_window.window_size
main_window.resize(size.width() / 3, size.height())
main_window.open_fileswitcher()

# Assert that the path shown in the fileswitcher is shorter
if PYQT5:
main_window.open_fileswitcher()
item_text = main_window.fileswitcher.list.currentItem().text()
assert '...' in item_text


if __name__ == "__main__":
pytest.main()
23 changes: 1 addition & 22 deletions spyder/config/main.py
Expand Up @@ -137,27 +137,6 @@
'umr/verbose': True,
'umr/namelist': [],
}),
('console',
{
'max_line_count': 500,
'wrap': True,
'single_tab': True,
'calltips': True,
'codecompletion/auto': True,
'codecompletion/enter_key': True,
'codecompletion/case_sensitive': True,
'show_elapsed_time': False,
'show_icontext': False,
'monitor/enabled': True,
'qt/api': 'default',
'matplotlib/backend/value': 0,
'light_background': True,
'merge_output_channels': os.name != 'nt',
'colorize_sys_stderr': os.name != 'nt',
'pythonstartup/default': True,
'pythonstartup/custom': False,
'ets_backend': 'qt4'
}),
('ipython_console',
{
'show_banner': True,
Expand Down Expand Up @@ -657,7 +636,7 @@
# or if you want to *rename* options, then you need to do a MAJOR update in
# version, e.g. from 3.0.0 to 4.0.0
# 3. You don't need to touch this value if you're just adding a new option
CONF_VERSION = '37.2.0'
CONF_VERSION = '38.0.0'

# Main configuration instance
try:
Expand Down
17 changes: 10 additions & 7 deletions spyder/plugins/configdialog.py
Expand Up @@ -39,6 +39,9 @@
from spyder.widgets.sourcecode.codeeditor import CodeEditor


HDPI_QT_PAGE = "http://doc.qt.io/qt-5/highdpi.html"


class ConfigAccessMixin(object):
"""Namespace for methods that access config storage"""
CONF_SECTION = None
Expand Down Expand Up @@ -872,9 +875,6 @@ def setup_page(self):
tear_off_box = newcb(_("Tear off menus"), 'tear_off_menus',
tip=_("Set this to detach any<br> "
"menu from the main window"))
high_dpi_scaling_box = newcb(_("Enable high DPI scaling"),
'high_dpi_scaling',
tip=_("Set this for high DPI displays"))
margin_box = newcb(_("Custom margin for panes:"),
'use_custom_margin')
margin_spin = self.create_spinbox("", _("pixels"), 'custom_margin',
Expand All @@ -901,7 +901,6 @@ def setup_page(self):
interface_layout.addWidget(verttabs_box)
interface_layout.addWidget(animated_box)
interface_layout.addWidget(tear_off_box)
interface_layout.addWidget(high_dpi_scaling_box)
interface_layout.addLayout(margins_layout)
interface_group.setLayout(interface_layout)

Expand Down Expand Up @@ -953,9 +952,13 @@ def setup_page(self):
# --- Screen resolution Group (hidpi)
screen_resolution_group = QGroupBox(_("Screen resolution"))
screen_resolution_bg = QButtonGroup(screen_resolution_group)
screen_resolution_label = QLabel(_("Configurations for highdpi screens, "
"See: <a href=\"http://doc.qt.io/qt-5/highdpi.html\">http://doc.qt.io/qt-5/highdpi.html</a><> "
"for more information"))
screen_resolution_label = QLabel(_("Configuration for high DPI "
"screens<br><br>"
"Please see "
"<a href=\"{0}\">{0}</a><> "
"for more information about "
"these options (in "
"English).").format(HDPI_QT_PAGE))
screen_resolution_label.setWordWrap(True)

normal_radio = self.create_radiobutton(
Expand Down
20 changes: 16 additions & 4 deletions spyder/plugins/console.py
Expand Up @@ -200,11 +200,15 @@ def register_plugin(self):
self.focus_changed.connect(self.main.plugin_focus_changed)
self.main.add_dockwidget(self)
# Connecting the following signal once the dockwidget has been created:
self.shell.traceback_available.connect(self.traceback_available)
self.shell.exception_occurred.connect(self.exception_occurred)

def traceback_available(self, text):
"""Traceback is available in the internal console: showing the
internal console automatically to warn the user"""
def exception_occurred(self, text, is_traceback):
"""Exception ocurred in the internal console.
Show a QMessageBox or the internal console to warn the user"""
# Skip errors without traceback
if not is_traceback and self.msgbox_traceback is None:
return

if CONF.get('main', 'show_internal_console_if_traceback', False):
self.dockwidget.show()
self.dockwidget.raise_()
Expand All @@ -230,6 +234,14 @@ def traceback_available(self, text):
self.error_traceback = ""
self.msgbox_traceback.show()
self.msgbox_traceback.finished.connect(self.close_msg)
self.msgbox_traceback.setDetailedText(' ')

# open show details (iterate over all buttons and click it)
for button in self.msgbox_traceback.buttons():
if (self.msgbox_traceback.buttonRole(button)
== QMessageBox.ActionRole):
button.click()
break

self.error_traceback += text
self.msgbox_traceback.setDetailedText(self.error_traceback)
Expand Down
6 changes: 3 additions & 3 deletions spyder/plugins/editor.py
Expand Up @@ -369,7 +369,7 @@ class Editor(SpyderPluginWidget):
DISABLE_ACTIONS_WHEN_HIDDEN = False # SpyderPluginWidget class attribute

# Signals
run_in_current_ipyclient = Signal(str, str, str, bool, bool, bool)
run_in_current_ipyclient = Signal(str, str, str, bool, bool, bool, bool)
exec_in_extconsole = Signal(str, bool)
redirect_stdio = Signal(bool)
open_dir = Signal(str)
Expand Down Expand Up @@ -2408,10 +2408,10 @@ def re_run_file(self):
(fname, wdir, args, interact, debug,
python, python_args, current, systerm,
post_mortem, clear_namespace) = self.__last_ec_exec
if current:
if not systerm:
self.run_in_current_ipyclient.emit(fname, wdir, args,
debug, post_mortem,
clear_namespace)
current, clear_namespace)
else:
self.main.open_external_console(fname, wdir, args, interact,
debug, python, python_args,
Expand Down

0 comments on commit bd99672

Please sign in to comment.