# -*- coding: utf-8 -*-
#
# Copyright © 2009-2010 Pierre Raybaut
# Licensed under the terms of the MIT License
# (see spyderlib/__init__.py for details)
"""External Console plugin"""
# pylint: disable=C0103
# pylint: disable=R0903
# pylint: disable=R0911
# pylint: disable=R0201
# Qt imports
from spyderlib.qt.QtGui import (QVBoxLayout, QMessageBox, QInputDialog,
QLineEdit, QPushButton, QGroupBox, QLabel,
QTabWidget, QFontComboBox, QHBoxLayout,
QButtonGroup, QWidget)
from spyderlib.qt.QtCore import SIGNAL, Qt
from spyderlib.qt.compat import getopenfilename
# Stdlib imports
import atexit
import os
import os.path as osp
import sys
import subprocess
# Local imports
from spyderlib.baseconfig import SCIENTIFIC_STARTUP, running_in_mac_app, _
from spyderlib.config import CONF
from spyderlib.utils import encoding, programs
from spyderlib.utils.misc import (get_error_match, get_python_executable,
remove_backslashes, is_python_script)
from spyderlib.utils.qthelpers import get_icon, create_action, mimedata2url
from spyderlib.widgets.tabs import Tabs
from spyderlib.widgets.externalshell.pythonshell import ExternalPythonShell
from spyderlib.widgets.externalshell.systemshell import ExternalSystemShell
from spyderlib.widgets.findreplace import FindReplace
from spyderlib.plugins import SpyderPluginWidget, PluginConfigPage
from spyderlib.plugins.runconfig import get_run_configuration
from spyderlib.py3compat import to_text_string, is_text_string, getcwd
from spyderlib import dependencies
MPL_REQVER = '>=1.0'
dependencies.add("matplotlib", _("Interactive data plotting in the consoles"),
required_version=MPL_REQVER)
class ExternalConsoleConfigPage(PluginConfigPage):
def __init__(self, plugin, parent):
PluginConfigPage.__init__(self, plugin, parent)
self.get_name = lambda: _("Console")
self.cus_exec_radio = None
self.pyexec_edit = None
def initialize(self):
PluginConfigPage.initialize(self)
self.connect(self.pyexec_edit, SIGNAL("textChanged(QString)"),
self.python_executable_changed)
self.connect(self.cus_exec_radio, SIGNAL("toggled(bool)"),
self.python_executable_switched)
def setup_page(self):
interface_group = QGroupBox(_("Interface"))
font_group = self.create_fontgroup(option=None, text=None,
fontfilters=QFontComboBox.MonospacedFonts)
newcb = self.create_checkbox
singletab_box = newcb(_("One tab per script"), 'single_tab')
showtime_box = newcb(_("Show elapsed time"), 'show_elapsed_time')
icontext_box = newcb(_("Show icons and text"), 'show_icontext')
# Interface Group
interface_layout = QVBoxLayout()
interface_layout.addWidget(singletab_box)
interface_layout.addWidget(showtime_box)
interface_layout.addWidget(icontext_box)
interface_group.setLayout(interface_layout)
# Source Code Group
display_group = QGroupBox(_("Source code"))
buffer_spin = self.create_spinbox(
_("Buffer: "), _(" lines"),
'max_line_count', min_=0, max_=1000000, step=100,
tip=_("Set maximum line count"))
wrap_mode_box = newcb(_("Wrap lines"), 'wrap')
merge_channels_box = newcb(
_("Merge process standard output/error channels"),
'merge_output_channels',
tip=_("Merging the output channels of the process means that\n"
"the standard error won't be written in red anymore,\n"
"but this has the effect of speeding up display."))
colorize_sys_stderr_box = newcb(
_("Colorize standard error channel using ANSI escape codes"),
'colorize_sys_stderr',
tip=_("This method is the only way to have colorized standard\n"
"error channel when the output channels have been "
"merged."))
self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
colorize_sys_stderr_box.setEnabled)
self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
colorize_sys_stderr_box.setChecked)
colorize_sys_stderr_box.setEnabled(
self.get_option('merge_output_channels'))
display_layout = QVBoxLayout()
display_layout.addWidget(buffer_spin)
display_layout.addWidget(wrap_mode_box)
display_layout.addWidget(merge_channels_box)
display_layout.addWidget(colorize_sys_stderr_box)
display_group.setLayout(display_layout)
# Background Color Group
bg_group = QGroupBox(_("Background color"))
bg_label = QLabel(_("This option will be applied the next time "
"a Python console or a terminal is opened."))
bg_label.setWordWrap(True)
lightbg_box = newcb(_("Light background (white color)"),
'light_background')
bg_layout = QVBoxLayout()
bg_layout.addWidget(bg_label)
bg_layout.addWidget(lightbg_box)
bg_group.setLayout(bg_layout)
# Advanced settings
source_group = QGroupBox(_("Source code"))
completion_box = newcb(_("Automatic code completion"),
'codecompletion/auto')
case_comp_box = newcb(_("Case sensitive code completion"),
'codecompletion/case_sensitive')
comp_enter_box = newcb(_("Enter key selects completion"),
'codecompletion/enter_key')
calltips_box = newcb(_("Display balloon tips"), 'calltips')
source_layout = QVBoxLayout()
source_layout.addWidget(completion_box)
source_layout.addWidget(case_comp_box)
source_layout.addWidget(comp_enter_box)
source_layout.addWidget(calltips_box)
source_group.setLayout(source_layout)
# UMR Group
umr_group = QGroupBox(_("User Module Reloader (UMR)"))
umr_label = QLabel(_("UMR forces Python to reload modules which were "
"imported when executing a \nscript in the "
"external console with the 'runfile' function."))
umr_enabled_box = newcb(_("Enable UMR"), 'umr/enabled',
msg_if_enabled=True, msg_warning=_(
"This option will enable the User Module Reloader (UMR) "
"in Python/IPython consoles. UMR forces Python to "
"reload deeply modules during import when running a "
"Python script using the Spyder's builtin function "
"runfile."
"
1. UMR may require to restart the "
"console in which it will be called "
"(otherwise only newly imported modules will be "
"reloaded when executing scripts)."
"
2. If errors occur when re-running a "
"PyQt-based program, please check that the Qt objects "
"are properly destroyed (e.g. you may have to use the "
"attribute Qt.WA_DeleteOnClose on your main "
"window, using the setAttribute method)"),
)
umr_verbose_box = newcb(_("Show reloaded modules list"),
'umr/verbose', msg_info=_(
"Please note that these changes will "
"be applied only to new consoles"))
umr_namelist_btn = QPushButton(
_("Set UMR excluded (not reloaded) modules"))
self.connect(umr_namelist_btn, SIGNAL('clicked()'),
self.plugin.set_umr_namelist)
umr_layout = QVBoxLayout()
umr_layout.addWidget(umr_label)
umr_layout.addWidget(umr_enabled_box)
umr_layout.addWidget(umr_verbose_box)
umr_layout.addWidget(umr_namelist_btn)
umr_group.setLayout(umr_layout)
# Python executable Group
pyexec_group = QGroupBox(_("Python executable"))
pyexec_bg = QButtonGroup(pyexec_group)
pyexec_label = QLabel(_("Select the Python interpreter executable "
"binary in which Spyder will run scripts:"))
def_exec_radio = self.create_radiobutton(
_("Default (i.e. the same as Spyder's)"),
'pythonexecutable/default',
button_group=pyexec_bg)
self.cus_exec_radio = self.create_radiobutton(
_("Use the following Python interpreter:"),
'pythonexecutable/custom',
button_group=pyexec_bg)
if os.name == 'nt':
filters = _("Executables")+" (*.exe)"
else:
filters = None
pyexec_file = self.create_browsefile('', 'pythonexecutable',
filters=filters)
for le in self.lineedits:
if self.lineedits[le][0] == 'pythonexecutable':
self.pyexec_edit = le
self.connect(def_exec_radio, SIGNAL("toggled(bool)"),
pyexec_file.setDisabled)
self.connect(self.cus_exec_radio, SIGNAL("toggled(bool)"),
pyexec_file.setEnabled)
pyexec_layout = QVBoxLayout()
pyexec_layout.addWidget(pyexec_label)
pyexec_layout.addWidget(def_exec_radio)
pyexec_layout.addWidget(self.cus_exec_radio)
pyexec_layout.addWidget(pyexec_file)
pyexec_group.setLayout(pyexec_layout)
# PYTHONSTARTUP replacement
pystartup_group = QGroupBox(_("PYTHONSTARTUP replacement"))
pystartup_bg = QButtonGroup(pystartup_group)
pystartup_label = QLabel(_("This option will override the "
"PYTHONSTARTUP environment variable which\n"
"defines the script to be executed during "
"the Python console startup."))
def_startup_radio = self.create_radiobutton(
_("Default PYTHONSTARTUP script"),
'pythonstartup/default',
button_group=pystartup_bg)
cus_startup_radio = self.create_radiobutton(
_("Use the following startup script:"),
'pythonstartup/custom',
button_group=pystartup_bg)
pystartup_file = self.create_browsefile('', 'pythonstartup', '',
filters=_("Python scripts")+\
" (*.py)")
self.connect(def_startup_radio, SIGNAL("toggled(bool)"),
pystartup_file.setDisabled)
self.connect(cus_startup_radio, SIGNAL("toggled(bool)"),
pystartup_file.setEnabled)
pystartup_layout = QVBoxLayout()
pystartup_layout.addWidget(pystartup_label)
pystartup_layout.addWidget(def_startup_radio)
pystartup_layout.addWidget(cus_startup_radio)
pystartup_layout.addWidget(pystartup_file)
pystartup_group.setLayout(pystartup_layout)
# Monitor Group
monitor_group = QGroupBox(_("Monitor"))
monitor_label = QLabel(_("The monitor provides introspection "
"features to console: code completion, "
"calltips and variable explorer. "
"Because it relies on several modules, "
"disabling the monitor may be useful "
"to accelerate console startup."))
monitor_label.setWordWrap(True)
monitor_box = newcb(_("Enable monitor"), 'monitor/enabled')
for obj in (completion_box, case_comp_box, comp_enter_box,
calltips_box):
self.connect(monitor_box, SIGNAL("toggled(bool)"), obj.setEnabled)
obj.setEnabled(self.get_option('monitor/enabled'))
monitor_layout = QVBoxLayout()
monitor_layout.addWidget(monitor_label)
monitor_layout.addWidget(monitor_box)
monitor_group.setLayout(monitor_layout)
# Qt Group
opts = [(_("Default library"), 'default'), ('PyQt4', 'pyqt'),
('PySide', 'pyside')]
qt_group = QGroupBox(_("Qt (PyQt/PySide)"))
qt_setapi_box = self.create_combobox(
_("Qt-Python bindings library selection:"), opts,
'qt/api', default='default',
tip=_("This option will act on
"
"libraries such as Matplotlib, guidata "
"or ETS"))
if self.get_option('pythonexecutable/default'):
interpreter = get_python_executable()
else:
interpreter = self.get_option('pythonexecutable')
has_pyqt4 = programs.is_module_installed('PyQt4',
interpreter=interpreter)
has_pyside = programs.is_module_installed('PySide',
interpreter=interpreter)
if has_pyside and not has_pyqt4:
self.set_option('qt/api', 'pyside')
qt_layout = QVBoxLayout()
qt_layout.addWidget(qt_setapi_box)
qt_group.setLayout(qt_layout)
qt_group.setEnabled(has_pyqt4 or has_pyside)
# PyQt Group
if has_pyqt4:
pyqt_group = QGroupBox(_("PyQt"))
setapi_box = self.create_combobox(
_("API selection for QString and QVariant objects:"),
((_("Default API"), 0), (_("API #1"), 1), (_("API #2"), 2)),
'pyqt/api_version', default=0,
tip=_("PyQt API #1 is the default
"
"API for Python 2. PyQt API #2 is "
"the default API for Python 3 and "
"is compatible with PySide."))
ignore_api_box = newcb(_("Ignore API change errors (sip.setapi)"),
'pyqt/ignore_sip_setapi_errors',
tip=_("Enabling this option will ignore
"
"errors when changing PyQt API. As "
"PyQt does not support dynamic API "
"changes, it is strongly recommended "
"to use this feature wisely, e.g. "
"for debugging purpose."))
try:
from sip import setapi #analysis:ignore
except ImportError:
setapi_box.setDisabled(True)
ignore_api_box.setDisabled(True)
pyqt_layout = QVBoxLayout()
pyqt_layout.addWidget(setapi_box)
pyqt_layout.addWidget(ignore_api_box)
pyqt_group.setLayout(pyqt_layout)
qt_layout.addWidget(pyqt_group)
# Matplotlib Group
mpl_group = QGroupBox(_("Matplotlib"))
mpl_backend_box = newcb('', 'matplotlib/backend/enabled', True)
mpl_backend_edit = self.create_lineedit(_("GUI backend:"),
'matplotlib/backend/value', "Qt4Agg",
tip=_("Set the GUI toolkit used by
"
"Matplotlib to show figures "
"(default: Qt4Agg)"),
alignment=Qt.Horizontal)
self.connect(mpl_backend_box, SIGNAL("toggled(bool)"),
mpl_backend_edit.setEnabled)
mpl_backend_layout = QHBoxLayout()
mpl_backend_layout.addWidget(mpl_backend_box)
mpl_backend_layout.addWidget(mpl_backend_edit)
mpl_backend_edit.setEnabled(
self.get_option('matplotlib/backend/enabled'))
mpl_installed = programs.is_module_installed('matplotlib')
mpl_layout = QVBoxLayout()
mpl_layout.addLayout(mpl_backend_layout)
mpl_group.setLayout(mpl_layout)
mpl_group.setEnabled(mpl_installed)
# ETS Group
ets_group = QGroupBox(_("Enthought Tool Suite"))
ets_label = QLabel(_("Enthought Tool Suite (ETS) supports "
"PyQt4 (qt4) and wxPython (wx) graphical "
"user interfaces."))
ets_label.setWordWrap(True)
ets_edit = self.create_lineedit(_("ETS_TOOLKIT:"), 'ets_backend',
alignment=Qt.Horizontal)
ets_layout = QVBoxLayout()
ets_layout.addWidget(ets_label)
ets_layout.addWidget(ets_edit)
ets_group.setLayout(ets_layout)
ets_group.setEnabled(programs.is_module_installed(
"enthought.etsconfig.api",
interpreter=interpreter))
tabs = QTabWidget()
tabs.addTab(self.create_tab(font_group, interface_group, display_group,
bg_group),
_("Display"))
tabs.addTab(self.create_tab(monitor_group, source_group),
_("Introspection"))
tabs.addTab(self.create_tab(pyexec_group, pystartup_group, umr_group),
_("Advanced settings"))
tabs.addTab(self.create_tab(qt_group, mpl_group, ets_group),
_("External modules"))
vlayout = QVBoxLayout()
vlayout.addWidget(tabs)
self.setLayout(vlayout)
def _auto_change_qt_api(self, pyexec):
"""Change automatically Qt API depending on
selected Python executable"""
has_pyqt4 = programs.is_module_installed('PyQt4', interpreter=pyexec)
has_pyside = programs.is_module_installed('PySide', interpreter=pyexec)
for cb in self.comboboxes:
if self.comboboxes[cb][0] == 'qt/api':
qt_setapi_cb = cb
if has_pyside and not has_pyqt4:
qt_setapi_cb.setCurrentIndex(2)
elif has_pyqt4 and not has_pyside:
qt_setapi_cb.setCurrentIndex(1)
else:
qt_setapi_cb.setCurrentIndex(0)
def python_executable_changed(self, pyexec):
"""Custom Python executable value has been changed"""
if not self.cus_exec_radio.isChecked():
return
if not is_text_string(pyexec):
pyexec = to_text_string(pyexec.toUtf8(), 'utf-8')
old_pyexec = self.get_option("pythonexecutable",
get_python_executable())
if pyexec != old_pyexec:
self._auto_change_qt_api(pyexec)
self.warn_python_compatibility(pyexec)
def python_executable_switched(self, custom):
"""Python executable default/custom radio button has been toggled"""
def_pyexec = get_python_executable()
cust_pyexec = self.pyexec_edit.text()
if not is_text_string(cust_pyexec):
cust_pyexec = to_text_string(cust_pyexec.toUtf8(), 'utf-8')
if def_pyexec != cust_pyexec:
pyexec = cust_pyexec if custom else def_pyexec
self._auto_change_qt_api(pyexec)
if custom:
self.warn_python_compatibility(cust_pyexec)
def warn_python_compatibility(self, pyexec):
if not osp.isfile(pyexec):
return
spyder_version = sys.version_info[0]
try:
cmd = [pyexec, "-c", "import sys; print(sys.version_info[0])"]
# subprocess.check_output is not present in python2.6 and 3.0
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
console_version = int(process.communicate()[0])
except IOError:
console_version = spyder_version
if spyder_version != console_version:
QMessageBox.warning(self, _('Warning'),
_("You selected a Python %d interpreter for the console "
"but Spyder is running on Python %d!.
"
"Although this is possible, we recommend you to install and "
"run Spyder directly with your selected interpreter, to avoid "
"seeing false warnings and errors due to the incompatible "
"syntax between these two Python versions."
) % (console_version, spyder_version), QMessageBox.Ok)
class ExternalConsole(SpyderPluginWidget):
"""
Console widget
"""
CONF_SECTION = 'console'
CONFIGWIDGET_CLASS = ExternalConsoleConfigPage
DISABLE_ACTIONS_WHEN_HIDDEN = False
def __init__(self, parent, light_mode):
SpyderPluginWidget.__init__(self, parent)
self.light_mode = light_mode
self.tabwidget = None
self.menu_actions = None
self.inspector = None # Object inspector plugin
self.historylog = None # History log plugin
self.variableexplorer = None # Variable explorer plugin
self.python_count = 0
self.terminal_count = 0
try:
from sip import setapi #analysis:ignore
except ImportError:
self.set_option('pyqt/ignore_sip_setapi_errors', False)
# Python executable selection (initializing default values as well)
executable = self.get_option('pythonexecutable',
get_python_executable())
if self.get_option('pythonexecutable/default'):
executable = get_python_executable()
# Python startup file selection
if not osp.isfile(self.get_option('pythonstartup', '')):
self.set_option('pythonstartup', SCIENTIFIC_STARTUP)
# default/custom settings are mutually exclusive:
self.set_option('pythonstartup/custom',
not self.get_option('pythonstartup/default'))
if not osp.isfile(executable):
# This is absolutely necessary, in case the Python interpreter
# executable has been moved since last Spyder execution (following
# a Python distribution upgrade for example)
self.set_option('pythonexecutable', get_python_executable())
elif executable.endswith('pythonw.exe'):
# That should not be necessary because this case is already taken
# care of by the `get_python_executable` function but, this was
# implemented too late, so we have to fix it here too, in case
# the Python executable has already been set with pythonw.exe:
self.set_option('pythonexecutable',
executable.replace("pythonw.exe", "python.exe"))
self.shellwidgets = []
self.filenames = []
self.icons = []
self.runfile_args = ""
# Initialize plugin
self.initialize_plugin()
layout = QVBoxLayout()
self.tabwidget = Tabs(self, self.menu_actions)
if hasattr(self.tabwidget, 'setDocumentMode')\
and not sys.platform == 'darwin':
# Don't set document mode to true on OSX because it generates
# a crash when the console is detached from the main window
# Fixes Issue 561
self.tabwidget.setDocumentMode(True)
self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
self.refresh_plugin)
self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
self.move_tab)
self.connect(self.main, SIGNAL("pythonpath_changed()"),
self.set_path)
self.tabwidget.set_close_function(self.close_console)
if sys.platform == 'darwin':
tab_container = QWidget()
tab_container.setObjectName('tab-container')
tab_layout = QHBoxLayout(tab_container)
tab_layout.setContentsMargins(0, 0, 0, 0)
tab_layout.addWidget(self.tabwidget)
layout.addWidget(tab_container)
else:
layout.addWidget(self.tabwidget)
# Find/replace widget
self.find_widget = FindReplace(self)
self.find_widget.hide()
self.register_widget_shortcuts("Editor", self.find_widget)
layout.addWidget(self.find_widget)
self.setLayout(layout)
# Accepting drops
self.setAcceptDrops(True)
def move_tab(self, index_from, index_to):
"""
Move tab (tabs themselves have already been moved by the tabwidget)
"""
filename = self.filenames.pop(index_from)
shell = self.shellwidgets.pop(index_from)
icons = self.icons.pop(index_from)
self.filenames.insert(index_to, filename)
self.shellwidgets.insert(index_to, shell)
self.icons.insert(index_to, icons)
self.emit(SIGNAL('update_plugin_title()'))
def get_shell_index_from_id(self, shell_id):
"""Return shellwidget index from id"""
for index, shell in enumerate(self.shellwidgets):
if id(shell) == shell_id:
return index
def close_console(self, index=None, from_ipyclient=False):
"""Close console tab from index or widget (or close current tab)"""
# Get tab index
if not self.tabwidget.count():
return
if index is None:
index = self.tabwidget.currentIndex()
# Detect what widget we are trying to close
for i, s in enumerate(self.shellwidgets):
if index == i:
shellwidget = s
# If the tab is an IPython kernel, try to detect if it has a client
# connected to it
if shellwidget.is_ipykernel:
ipyclients = self.main.ipyconsole.get_clients()
if ipyclients:
for ic in ipyclients:
if ic.kernel_widget_id == id(shellwidget):
connected_ipyclient = True
break
else:
connected_ipyclient = False
else:
connected_ipyclient = False
# Closing logic
if not shellwidget.is_ipykernel or from_ipyclient or \
not connected_ipyclient:
self.tabwidget.widget(index).close()
self.tabwidget.removeTab(index)
self.filenames.pop(index)
self.shellwidgets.pop(index)
self.icons.pop(index)
self.emit(SIGNAL('update_plugin_title()'))
else:
QMessageBox.question(self, _('Trying to kill a kernel?'),
_("You can't close this kernel because it has one or more "
"consoles connected to it.
"
"You need to close them instead or you can kill the kernel "
"using the second button from right to left."),
QMessageBox.Ok)
def set_variableexplorer(self, variableexplorer):
"""Set variable explorer plugin"""
self.variableexplorer = variableexplorer
def set_path(self):
"""Set consoles PYTHONPATH if changed by the user"""
from spyderlib.widgets.externalshell import pythonshell
for sw in self.shellwidgets:
if isinstance(sw, pythonshell.ExternalPythonShell):
if sw.is_interpreter and sw.is_running():
sw.path = self.main.get_spyder_pythonpath()
sw.shell.path = sw.path
def __find_python_shell(self, interpreter_only=False):
current_index = self.tabwidget.currentIndex()
if current_index == -1:
return
from spyderlib.widgets.externalshell import pythonshell
for index in [current_index]+list(range(self.tabwidget.count())):
shellwidget = self.tabwidget.widget(index)
if isinstance(shellwidget, pythonshell.ExternalPythonShell):
if interpreter_only and not shellwidget.is_interpreter:
continue
elif not shellwidget.is_running():
continue
else:
self.tabwidget.setCurrentIndex(index)
return shellwidget
def get_current_shell(self):
"""
Called by object inspector to retrieve the current shell instance
"""
shellwidget = self.__find_python_shell()
return shellwidget.shell
def get_running_python_shell(self):
"""
Called by object inspector to retrieve a running Python shell instance
"""
current_index = self.tabwidget.currentIndex()
if current_index == -1:
return
from spyderlib.widgets.externalshell import pythonshell
shellwidgets = [self.tabwidget.widget(index)
for index in range(self.tabwidget.count())]
shellwidgets = [_w for _w in shellwidgets
if isinstance(_w, pythonshell.ExternalPythonShell) \
and _w.is_running()]
if shellwidgets:
# First, iterate on interpreters only:
for shellwidget in shellwidgets:
if shellwidget.is_interpreter:
return shellwidget.shell
else:
return shellwidgets[0].shell
def run_script_in_current_shell(self, filename, wdir, args, debug):
"""Run script in current shell, if any"""
norm = lambda text: remove_backslashes(to_text_string(text))
line = "%s('%s'" % ('debugfile' if debug else 'runfile',
norm(filename))
if args:
line += ", args='%s'" % norm(args)
if wdir:
line += ", wdir='%s'" % norm(wdir)
line += ")"
if not self.execute_python_code(line, interpreter_only=True):
QMessageBox.warning(self, _('Warning'),
_("No Python console is currently selected to run %s."
"
Please select or open a new Python console "
"and try again."
) % osp.basename(norm(filename)), QMessageBox.Ok)
else:
self.visibility_changed(True)
self.raise_()
def set_current_shell_working_directory(self, directory):
"""Set current shell working directory"""
shellwidget = self.__find_python_shell()
if shellwidget is not None:
directory = encoding.to_unicode_from_fs(directory)
shellwidget.shell.set_cwd(directory)
def execute_python_code(self, lines, interpreter_only=False):
"""Execute Python code in an already opened Python interpreter"""
shellwidget = self.__find_python_shell(
interpreter_only=interpreter_only)
if (shellwidget is not None) and (not shellwidget.is_ipykernel):
shellwidget.shell.execute_lines(to_text_string(lines))
self.activateWindow()
shellwidget.shell.setFocus()
return True
else:
return False
def pdb_has_stopped(self, fname, lineno, shellwidget):
"""Python debugger has just stopped at frame (fname, lineno)"""
# This is a unique form of the edit_goto signal that is intended to
# prevent keyboard input from accidentally entering the editor
# during repeated, rapid entry of debugging commands.
self.emit(SIGNAL("edit_goto(QString,int,QString,bool)"),
fname, lineno, '', False)
if shellwidget.is_ipykernel:
# Focus client widget, not kernel
ipw = self.main.ipyconsole.get_focus_widget()
self.main.ipyconsole.activateWindow()
ipw.setFocus()
else:
self.activateWindow()
shellwidget.shell.setFocus()
def set_spyder_breakpoints(self):
"""Set all Spyder breakpoints into all shells"""
for shellwidget in self.shellwidgets:
shellwidget.shell.set_spyder_breakpoints()
def start(self, fname, wdir=None, args='', interact=False, debug=False,
python=True, ipykernel=False, ipyclient=None,
give_ipyclient_focus=True, python_args=''):
"""
Start new console
fname:
string: filename of script to run
None: open an interpreter
wdir: working directory
args: command line options of the Python script
interact: inspect script interactively after its execution
debug: run pdb
python: True: Python interpreter, False: terminal
ipykernel: True: IPython kernel
ipyclient: True: Automatically create an IPython client
python_args: additionnal Python interpreter command line options
(option "-u" is mandatory, see widgets.externalshell package)
"""
# Note: fname is None <=> Python interpreter
if fname is not None and not is_text_string(fname):
fname = to_text_string(fname)
if wdir is not None and not is_text_string(wdir):
wdir = to_text_string(wdir)
if fname is not None and fname in self.filenames:
index = self.filenames.index(fname)
if self.get_option('single_tab'):
old_shell = self.shellwidgets[index]
if old_shell.is_running():
runconfig = get_run_configuration(fname)
if runconfig is None or runconfig.show_kill_warning:
answer = QMessageBox.question(self, self.get_plugin_title(),
_("%s is already running in a separate process.\n"
"Do you want to kill the process before starting "
"a new one?") % osp.basename(fname),
QMessageBox.Yes | QMessageBox.Cancel)
else:
answer = QMessageBox.Yes
if answer == QMessageBox.Yes:
old_shell.process.kill()
old_shell.process.waitForFinished()
else:
return
self.close_console(index)
else:
index = self.tabwidget.count()
# Creating a new external shell
pythonpath = self.main.get_spyder_pythonpath()
light_background = self.get_option('light_background')
show_elapsed_time = self.get_option('show_elapsed_time')
if python:
if self.get_option('pythonexecutable/default'):
pythonexecutable = get_python_executable()
else:
pythonexecutable = self.get_option('pythonexecutable')
if self.get_option('pythonstartup/default') or ipykernel:
pythonstartup = None
else:
pythonstartup = self.get_option('pythonstartup', None)
monitor_enabled = self.get_option('monitor/enabled')
if self.get_option('matplotlib/backend/enabled'):
mpl_backend = self.get_option('matplotlib/backend/value')
else:
mpl_backend = None
ets_backend = self.get_option('ets_backend')
qt_api = self.get_option('qt/api')
if qt_api not in ('pyqt', 'pyside'):
qt_api = None
pyqt_api = self.get_option('pyqt/api_version')
ignore_sip_setapi_errors = self.get_option(
'pyqt/ignore_sip_setapi_errors')
merge_output_channels = self.get_option('merge_output_channels')
colorize_sys_stderr = self.get_option('colorize_sys_stderr')
umr_enabled = self.get_option('umr/enabled')
umr_namelist = self.get_option('umr/namelist')
umr_verbose = self.get_option('umr/verbose')
ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout')
ar_state = CONF.get('variable_explorer', 'autorefresh')
# CRUCIAL NOTE FOR IPYTHON KERNELS:
# autorefresh needs to be on so that our monitor
# can find __ipythonkernel__ in the globals namespace
# *after* the kernel has been started.
# Without the ns refresh provided by autorefresh, a
# client is *never* started (although the kernel is)
# Fix Issue 1595
if not ar_state and ipykernel:
ar_state = True
if self.light_mode:
from spyderlib.plugins.variableexplorer import VariableExplorer
sa_settings = VariableExplorer.get_settings()
else:
sa_settings = None
shellwidget = ExternalPythonShell(self, fname, wdir,
interact, debug, path=pythonpath,
python_args=python_args,
ipykernel=ipykernel,
arguments=args, stand_alone=sa_settings,
pythonstartup=pythonstartup,
pythonexecutable=pythonexecutable,
umr_enabled=umr_enabled, umr_namelist=umr_namelist,
umr_verbose=umr_verbose, ets_backend=ets_backend,
monitor_enabled=monitor_enabled,
mpl_backend=mpl_backend,
qt_api=qt_api, pyqt_api=pyqt_api,
ignore_sip_setapi_errors=ignore_sip_setapi_errors,
merge_output_channels=merge_output_channels,
colorize_sys_stderr=colorize_sys_stderr,
autorefresh_timeout=ar_timeout,
autorefresh_state=ar_state,
light_background=light_background,
menu_actions=self.menu_actions,
show_buttons_inside=False,
show_elapsed_time=show_elapsed_time)
self.connect(shellwidget, SIGNAL('pdb(QString,int)'),
lambda fname, lineno, shellwidget=shellwidget:
self.pdb_has_stopped(fname, lineno, shellwidget))
self.register_widget_shortcuts("Console", shellwidget.shell)
else:
if os.name == 'posix':
cmd = 'gnome-terminal'
args = []
if programs.is_program_installed(cmd):
if wdir:
args.extend(['--working-directory=%s' % wdir])
programs.run_program(cmd, args)
return
cmd = 'konsole'
if programs.is_program_installed(cmd):
if wdir:
args.extend(['--workdir', wdir])
programs.run_program(cmd, args)
return
shellwidget = ExternalSystemShell(self, wdir, path=pythonpath,
light_background=light_background,
menu_actions=self.menu_actions,
show_buttons_inside=False,
show_elapsed_time=show_elapsed_time)
# Code completion / calltips
shellwidget.shell.setMaximumBlockCount(
self.get_option('max_line_count') )
shellwidget.shell.set_font( self.get_plugin_font() )
shellwidget.shell.toggle_wrap_mode( self.get_option('wrap') )
shellwidget.shell.set_calltips( self.get_option('calltips') )
shellwidget.shell.set_codecompletion_auto(
self.get_option('codecompletion/auto') )
shellwidget.shell.set_codecompletion_case(
self.get_option('codecompletion/case_sensitive') )
shellwidget.shell.set_codecompletion_enter(
self.get_option('codecompletion/enter_key') )
if python and self.inspector is not None:
shellwidget.shell.set_inspector(self.inspector)
shellwidget.shell.set_inspector_enabled(
CONF.get('inspector', 'connect/python_console'))
if self.historylog is not None:
self.historylog.add_history(shellwidget.shell.history_filename)
self.connect(shellwidget.shell,
SIGNAL('append_to_history(QString,QString)'),
self.historylog.append_to_history)
self.connect(shellwidget.shell, SIGNAL("go_to_error(QString)"),
self.go_to_error)
self.connect(shellwidget.shell, SIGNAL("focus_changed()"),
lambda: self.emit(SIGNAL("focus_changed()")))
if python:
if self.main.editor is not None:
self.connect(shellwidget, SIGNAL('open_file(QString,int)'),
self.open_file_in_spyder)
if fname is None:
if ipykernel:
# Connect client to any possible error while starting the
# kernel
ipyclient.connect(shellwidget,
SIGNAL("ipython_kernel_start_error(QString)"),
lambda error: ipyclient.show_kernel_error(error))
# Detect if kernel and frontend match or not
# Don't apply this for our Mac app because it's
# failing, see Issue 2006
if self.get_option('pythonexecutable/custom') and \
not running_in_mac_app():
frontend_ver = programs.get_module_version('IPython')
old_vers = ['1', '2']
if any([frontend_ver.startswith(v) for v in old_vers]):
frontend_ver = '<3.0'
else:
frontend_ver = '>=3.0'
pyexec = self.get_option('pythonexecutable')
kernel_and_frontend_match = \
programs.is_module_installed('IPython',
version=frontend_ver,
interpreter=pyexec)
else:
kernel_and_frontend_match = True
# Create a a kernel tab only if frontend and kernel
# versions match
if kernel_and_frontend_match:
tab_name = _("Kernel")
tab_icon1 = get_icon('ipython_console.png')
tab_icon2 = get_icon('ipython_console_t.png')
self.connect(shellwidget,
SIGNAL('create_ipython_client(QString)'),
lambda cf: self.register_ipyclient(cf,
ipyclient,
shellwidget,
give_focus=give_ipyclient_focus))
else:
shellwidget.emit(
SIGNAL("ipython_kernel_start_error(QString)"),
_("Either:"
"