1
# -*- coding: utf-8 -*-
3
# Copyright © 2009-2010 Pierre Raybaut
4
# Licensed under the terms of the MIT License
5
# (see spyderlib/__init__.py for details)
7
"""External Console plugin"""
9
# pylint: disable=C0103
10
# pylint: disable=R0903
11
# pylint: disable=R0911
12
# pylint: disable=R0201
14
from spyderlib.qt.QtGui import (QVBoxLayout, QMessageBox, QInputDialog,
15
QLineEdit, QPushButton, QGroupBox, QLabel,
16
QTabWidget, QFontComboBox, QHBoxLayout,
17
QApplication, QCursor)
18
from spyderlib.qt.QtCore import SIGNAL, Qt
19
from spyderlib.qt.compat import getopenfilename
29
from spyderlib.baseconfig import _, SCIENTIFIC_STARTUP
30
from spyderlib.config import CONF
31
from spyderlib.guiconfig import get_icon
32
from spyderlib.utils import programs
33
from spyderlib.utils.misc import (get_error_match, get_python_executable,
34
remove_trailing_single_backslash,
36
from spyderlib.utils.qthelpers import create_action, mimedata2url
37
from spyderlib.widgets.tabs import Tabs
38
from spyderlib.widgets.externalshell.pythonshell import ExternalPythonShell
39
from spyderlib.widgets.externalshell.systemshell import ExternalSystemShell
40
from spyderlib.widgets.findreplace import FindReplace
41
from spyderlib.plugins import SpyderPluginWidget, PluginConfigPage
44
class ExternalConsoleConfigPage(PluginConfigPage):
45
def __init__(self, plugin, parent):
46
PluginConfigPage.__init__(self, plugin, parent)
47
self.get_name = lambda: _("Console")
50
interface_group = QGroupBox(_("Interface"))
51
font_group = self.create_fontgroup(option=None, text=None,
52
fontfilters=QFontComboBox.MonospacedFonts)
53
newcb = self.create_checkbox
54
singletab_box = newcb(_("One tab per script"), 'single_tab')
55
showtime_box = newcb(_("Show elapsed time"), 'show_elapsed_time')
56
icontext_box = newcb(_("Show icons and text"), 'show_icontext')
59
interface_layout = QVBoxLayout()
60
interface_layout.addWidget(singletab_box)
61
interface_layout.addWidget(showtime_box)
62
interface_layout.addWidget(icontext_box)
63
interface_group.setLayout(interface_layout)
66
display_group = QGroupBox(_("Source code"))
67
buffer_spin = self.create_spinbox(
68
_("Buffer: "), _(" lines"),
69
'max_line_count', min_=0, max_=1000000, step=100,
70
tip=_("Set maximum line count"))
71
wrap_mode_box = newcb(_("Wrap lines"), 'wrap')
72
merge_channels_box = newcb(
73
_("Merge process standard output/error channels"),
74
'merge_output_channels',
75
tip=_("Merging the output channels of the process means that\n"
76
"the standard error won't be written in red anymore,\n"
77
"but this has the effect of speeding up display."))
78
colorize_sys_stderr_box = newcb(
79
_("Colorize standard error channel using ANSI escape codes"),
80
'colorize_sys_stderr',
81
tip=_("This method is the only way to have colorized standard\n"
82
"error channel when the output channels have been "
84
self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
85
colorize_sys_stderr_box.setEnabled)
86
self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
87
colorize_sys_stderr_box.setChecked)
88
colorize_sys_stderr_box.setEnabled(
89
self.get_option('merge_output_channels'))
91
display_layout = QVBoxLayout()
92
display_layout.addWidget(buffer_spin)
93
display_layout.addWidget(wrap_mode_box)
94
display_layout.addWidget(merge_channels_box)
95
display_layout.addWidget(colorize_sys_stderr_box)
96
display_group.setLayout(display_layout)
98
# Background Color Group
99
bg_group = QGroupBox(_("Background color"))
100
bg_label = QLabel(_("This option will be applied the next time "
101
"a Python console or a terminal is opened."))
102
bg_label.setWordWrap(True)
103
lightbg_box = newcb(_("Light background (white color)"),
105
bg_layout = QVBoxLayout()
106
bg_layout.addWidget(bg_label)
107
bg_layout.addWidget(lightbg_box)
108
bg_group.setLayout(bg_layout)
111
source_group = QGroupBox(_("Source code"))
112
completion_box = newcb(_("Automatic code completion"),
113
'codecompletion/auto')
114
case_comp_box = newcb(_("Case sensitive code completion"),
115
'codecompletion/case_sensitive')
116
show_single_box = newcb(_("Show single completion"),
117
'codecompletion/show_single')
118
comp_enter_box = newcb(_("Enter key selects completion"),
119
'codecompletion/enter_key')
120
calltips_box = newcb(_("Balloon tips"), 'calltips')
121
inspector_box = newcb(
122
_("Automatic notification to object inspector"),
123
'object_inspector', default=True,
124
tip=_("If this option is enabled, object inspector\n"
125
"will automatically show informations on functions\n"
126
"entered in console (this is triggered when entering\n"
127
"a left parenthesis after a valid function name)"))
129
source_layout = QVBoxLayout()
130
source_layout.addWidget(completion_box)
131
source_layout.addWidget(case_comp_box)
132
source_layout.addWidget(show_single_box)
133
source_layout.addWidget(comp_enter_box)
134
source_layout.addWidget(calltips_box)
135
source_layout.addWidget(inspector_box)
136
source_group.setLayout(source_layout)
139
umd_group = QGroupBox(_("User Module Deleter (UMD)"))
140
umd_label = QLabel(_("UMD forces Python to reload modules which were "
141
"imported when executing a \nscript in the "
142
"external console with the 'runfile' function."))
143
umd_enabled_box = newcb(_("Enable UMD"), 'umd/enabled',
144
msg_if_enabled=True, msg_warning=_(
145
"This option will enable the User Module Deleter (UMD) "
146
"in Python interpreters. UMD forces Python to "
147
"reload deeply modules during import when running a "
148
"Python script using the Spyder's builtin function "
150
"<br><br><b>1.</b> UMD may require to restart the "
151
"Python interpreter in which it will be called "
152
"(otherwise only newly imported modules will be "
153
"reloaded when executing scripts)."
154
"<br><br><b>2.</b> If errors occur when re-running a "
155
"PyQt-based program, please check that the Qt objects "
156
"are properly destroyed (e.g. you may have to use the "
157
"attribute <b>Qt.WA_DeleteOnClose</b> on your main "
158
"window, using the <b>setAttribute</b> method)"),
160
umd_verbose_box = newcb(_("Show reloaded modules list"),
161
'umd/verbose', msg_info=_(
162
"Please note that these changes will "
163
"be applied only to new Python interpreters"))
164
umd_namelist_btn = QPushButton(
165
_("Set UMD excluded (not reloaded) modules"))
166
self.connect(umd_namelist_btn, SIGNAL('clicked()'),
167
self.plugin.set_umd_namelist)
169
umd_layout = QVBoxLayout()
170
umd_layout.addWidget(umd_label)
171
umd_layout.addWidget(umd_enabled_box)
172
umd_layout.addWidget(umd_verbose_box)
173
umd_layout.addWidget(umd_namelist_btn)
174
umd_group.setLayout(umd_layout)
176
# Python executable Group
177
pyexec_group = QGroupBox(_("Python executable"))
178
pyexec_label = QLabel(_("Path to Python interpreter "
179
"executable binary:"))
181
filters = _("Executables")+" (*.exe)"
184
pyexec_file = self.create_browsefile('', 'pythonexecutable',
187
pyexec_layout = QVBoxLayout()
188
pyexec_layout.addWidget(pyexec_label)
189
pyexec_layout.addWidget(pyexec_file)
190
pyexec_group.setLayout(pyexec_layout)
193
startup_group = QGroupBox(_("Startup"))
194
pystartup_box = newcb(_("Open a Python interpreter at startup"),
195
'open_python_at_startup')
197
startup_layout = QVBoxLayout()
198
startup_layout.addWidget(pystartup_box)
199
startup_group.setLayout(startup_layout)
201
# PYTHONSTARTUP replacement
202
pystartup_group = QGroupBox(_("PYTHONSTARTUP replacement"))
203
pystartup_label = QLabel(_("This option will override the "
204
"PYTHONSTARTUP environment variable which\n"
205
"defines the script to be executed during "
206
"the Python interpreter startup."))
207
default_radio = self.create_radiobutton(
208
_("Default PYTHONSTARTUP script"),
209
'pythonstartup/default', True)
210
custom_radio = self.create_radiobutton(
211
_("Use the following startup script:"),
212
'pythonstartup/custom', False)
213
pystartup_file = self.create_browsefile('', 'pythonstartup', '',
214
filters=_("Python scripts")+\
216
self.connect(default_radio, SIGNAL("toggled(bool)"),
217
pystartup_file.setDisabled)
218
self.connect(custom_radio, SIGNAL("toggled(bool)"),
219
pystartup_file.setEnabled)
221
pystartup_layout = QVBoxLayout()
222
pystartup_layout.addWidget(pystartup_label)
223
pystartup_layout.addWidget(default_radio)
224
pystartup_layout.addWidget(custom_radio)
225
pystartup_layout.addWidget(pystartup_file)
226
pystartup_group.setLayout(pystartup_layout)
229
monitor_group = QGroupBox(_("Monitor"))
230
monitor_label = QLabel(_("The monitor provides introspection "
231
"features to console: code completion, "
232
"calltips and variable explorer. "
233
"Because it relies on several modules, "
234
"disabling the monitor may be useful "
235
"to accelerate console startup."))
236
monitor_label.setWordWrap(True)
237
monitor_box = newcb(_("Enable monitor"), 'monitor/enabled')
238
for obj in (completion_box, case_comp_box, show_single_box,
239
comp_enter_box, calltips_box):
240
self.connect(monitor_box, SIGNAL("toggled(bool)"), obj.setEnabled)
241
obj.setEnabled(self.get_option('monitor/enabled'))
243
monitor_layout = QVBoxLayout()
244
monitor_layout.addWidget(monitor_label)
245
monitor_layout.addWidget(monitor_box)
246
monitor_group.setLayout(monitor_layout)
249
# Do not test if PyQt4 or PySide is installed with the function
250
# spyderlib.utils.programs.is_module_installed because it will
251
# fail (both libraries can't be imported at the same time):
253
imp.find_module('PyQt4')
258
imp.find_module('PySide')
264
opts.append( ('PyQt4', 'pyqt') )
266
opts.append( ('PySide', 'pyside') )
267
qt_group = QGroupBox(_("Qt (PyQt/PySide)"))
268
qt_setapi_box = self.create_combobox(
269
_("Qt-Python bindings library selection:"),
270
[(_("Default library"), 'default')]+opts,
271
'qt/api', default='default', tip=_(
272
"""This option will act on libraries such as Matplotlib, guidata or ETS"""))
273
qt_hook_box = newcb(_("Install Spyder's input hook for Qt"),
274
'qt/install_inputhook',
276
"""PyQt installs an input hook that allows creating and interacting
277
with Qt widgets in an interactive interpreter without blocking it.
278
On Windows platforms, it is strongly recommended to replace it by Spyder's.
279
Regarding PySide, note that it does not install an input hook, so it is
280
required to enable this feature in order to be able to manipulate PySide/Qt
281
objects interactively."""))
283
qt_layout = QVBoxLayout()
284
qt_layout.addWidget(qt_setapi_box)
285
qt_layout.addWidget(qt_hook_box)
286
qt_group.setLayout(qt_layout)
287
qt_group.setEnabled(has_pyqt4 or has_pyside)
291
pyqt_group = QGroupBox(_("PyQt"))
292
setapi_box = self.create_combobox(
293
_("API selection for QString and QVariant objects:"),
294
((_("Default API"), 0), (_("API #1"), 1), (_("API #2"), 2)),
295
'pyqt/api_version', default=0, tip=_(
296
"""PyQt API #1 is the default API for Python 2. PyQt API #2 is the default
297
API for Python 3 and is compatible with PySide.
298
Note that switching to API #2 may require to enable the Matplotlib patch."""))
299
ignore_api_box = newcb(_("Ignore API change errors (sip.setapi)"),
300
'pyqt/ignore_sip_setapi_errors', tip=_(
301
"""Enabling this option will ignore errors when changing PyQt API.
302
As PyQt does not support dynamic API changes, it is strongly recommended
303
to use this feature wisely, e.g. for debugging purpose."""))
305
from sip import setapi #analysis:ignore
307
setapi_box.setDisabled(True)
308
ignore_api_box.setDisabled(True)
310
pyqt_layout = QVBoxLayout()
311
pyqt_layout.addWidget(setapi_box)
312
pyqt_layout.addWidget(ignore_api_box)
313
pyqt_group.setLayout(pyqt_layout)
314
qt_layout.addWidget(pyqt_group)
317
mpl_group = QGroupBox(_("Matplotlib"))
318
mpl_backend_box = newcb('', 'matplotlib/backend/enabled', True)
319
mpl_backend_edit = self.create_lineedit(_("GUI backend:"),
320
'matplotlib/backend/value', "Qt4Agg",
321
_("Set the GUI toolkit used by Matplotlib to "
322
"show figures (default: Qt4Agg)"),
323
alignment=Qt.Horizontal)
324
self.connect(mpl_backend_box, SIGNAL("toggled(bool)"),
325
mpl_backend_edit.setEnabled)
326
mpl_backend_layout = QHBoxLayout()
327
mpl_backend_layout.addWidget(mpl_backend_box)
328
mpl_backend_layout.addWidget(mpl_backend_edit)
329
mpl_backend_edit.setEnabled(
330
self.get_option('matplotlib/backend/enabled'))
331
mpl_patch_box = newcb(_("Patch Matplotlib figures"),
332
'matplotlib/patch', False)
333
mpl_patch_label = QLabel(_("Patching Matplotlib library will add a "
334
"button to customize figure options "
335
"(Qt4Agg only) and fix some issues."))
336
mpl_patch_label.setWordWrap(True)
337
self.connect(mpl_patch_box, SIGNAL("toggled(bool)"),
338
mpl_patch_label.setEnabled)
340
mpl_installed = programs.is_module_installed('matplotlib')
342
from spyderlib import mpl_patch
343
if not mpl_patch.is_available():
345
mpl_patch_label.hide()
347
mpl_layout = QVBoxLayout()
348
mpl_layout.addLayout(mpl_backend_layout)
349
mpl_layout.addWidget(mpl_patch_box)
350
mpl_layout.addWidget(mpl_patch_label)
351
mpl_group.setLayout(mpl_layout)
352
mpl_group.setEnabled(mpl_installed)
355
ets_group = QGroupBox(_("Enthought Tool Suite"))
356
ets_label = QLabel(_("Enthought Tool Suite (ETS) supports "
357
"PyQt4 (qt4) and wxPython (wx) graphical "
359
ets_label.setWordWrap(True)
360
ets_edit = self.create_lineedit(_("ETS_TOOLKIT:"), 'ets_backend',
361
default='qt4', alignment=Qt.Horizontal)
363
ets_layout = QVBoxLayout()
364
ets_layout.addWidget(ets_label)
365
ets_layout.addWidget(ets_edit)
366
ets_group.setLayout(ets_layout)
367
ets_group.setEnabled(programs.is_module_installed(
368
"enthought.etsconfig.api"))
371
tabs.addTab(self.create_tab(font_group, interface_group, display_group,
374
tabs.addTab(self.create_tab(monitor_group, source_group),
376
tabs.addTab(self.create_tab(pyexec_group, startup_group,
377
pystartup_group, umd_group),
378
_("Advanced settings"))
379
tabs.addTab(self.create_tab(qt_group, mpl_group, ets_group),
380
_("External modules"))
382
vlayout = QVBoxLayout()
383
vlayout.addWidget(tabs)
384
self.setLayout(vlayout)
387
class ExternalConsole(SpyderPluginWidget):
391
CONF_SECTION = 'console'
392
CONFIGWIDGET_CLASS = ExternalConsoleConfigPage
393
def __init__(self, parent, light_mode):
394
SpyderPluginWidget.__init__(self, parent)
395
self.light_mode = light_mode
396
self.tabwidget = None
397
self.menu_actions = None
399
self.inspector = None # Object inspector plugin
400
self.historylog = None # History log plugin
401
self.variableexplorer = None # Variable explorer plugin
403
self.python_count = 0
404
self.terminal_count = 0
407
from sip import setapi #analysis:ignore
409
self.set_option('pyqt/ignore_sip_setapi_errors', False)
411
scientific = programs.is_module_installed('numpy') and\
412
programs.is_module_installed('scipy') and\
413
programs.is_module_installed('matplotlib')
414
if self.get_option('pythonstartup/default', None) is None:
415
self.set_option('pythonstartup/default', not scientific)
416
if not osp.isfile(self.get_option('pythonstartup', '')):
417
self.set_option('pythonstartup', SCIENTIFIC_STARTUP)
418
self.set_option('pythonstartup/default', not scientific)
419
# default/custom settings are mutually exclusive:
420
self.set_option('pythonstartup/custom',
421
not self.get_option('pythonstartup/default'))
423
executable = self.get_option('pythonexecutable',
424
get_python_executable())
425
if not osp.isfile(executable):
426
# This is absolutely necessary, in case the Python interpreter
427
# executable has been moved since last Spyder execution (following
428
# a Python distribution upgrade for example)
429
self.set_option('pythonexecutable', get_python_executable())
430
elif executable.endswith('pythonw.exe'):
431
# That should not be necessary because this case is already taken
432
# care of by the `get_python_executable` function but, this was
433
# implemented too late, so we have to fix it here too, in case
434
# the Python executable has already been set with pythonw.exe:
435
self.set_option('pythonexecutable',
436
executable.replace("pythonw.exe", "python.exe"))
438
self.shellwidgets = []
441
self.runfile_args = ""
444
self.initialize_plugin()
446
layout = QVBoxLayout()
447
self.tabwidget = Tabs(self, self.menu_actions)
448
if hasattr(self.tabwidget, 'setDocumentMode')\
449
and not sys.platform == 'darwin':
450
# Don't set document mode to true on OSX because it generates
451
# a crash when the console is detached from the main window
453
self.tabwidget.setDocumentMode(True)
454
self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
456
self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
459
self.tabwidget.set_close_function(self.close_console)
461
layout.addWidget(self.tabwidget)
463
# Find/replace widget
464
self.find_widget = FindReplace(self)
465
self.find_widget.hide()
466
self.register_widget_shortcuts("Editor", self.find_widget)
468
layout.addWidget(self.find_widget)
470
self.setLayout(layout)
473
self.setAcceptDrops(True)
475
def move_tab(self, index_from, index_to):
477
Move tab (tabs themselves have already been moved by the tabwidget)
479
filename = self.filenames.pop(index_from)
480
shell = self.shellwidgets.pop(index_from)
481
icons = self.icons.pop(index_from)
483
self.filenames.insert(index_to, filename)
484
self.shellwidgets.insert(index_to, shell)
485
self.icons.insert(index_to, icons)
486
self.emit(SIGNAL('update_plugin_title()'))
488
def get_shell_index_from_id(self, shell_id):
489
"""Return shellwidget index from id"""
490
for index, shell in enumerate(self.shellwidgets):
491
if id(shell) == shell_id:
494
def close_console(self, index=None, from_ipython_client=False):
495
"""Close console tab from index or widget (or close current tab)"""
497
if not self.tabwidget.count():
500
index = self.tabwidget.currentIndex()
502
# Detect what widget we are trying to close
503
for i, s in enumerate(self.shellwidgets):
507
# If the tab is an IPython kernel, try to detect if it has a client
509
if shellwidget.is_ipython_kernel:
510
ipyclients = self.main.ipyconsole.get_clients()
512
for ic in ipyclients:
513
if ic.kernel_widget_id == id(shellwidget):
514
connected_ipyclient = True
517
connected_ipyclient = False
519
connected_ipyclient = False
522
if not shellwidget.is_ipython_kernel or from_ipython_client or \
523
not connected_ipyclient:
524
self.tabwidget.widget(index).close()
525
self.tabwidget.removeTab(index)
526
self.filenames.pop(index)
527
self.shellwidgets.pop(index)
528
self.icons.pop(index)
529
self.emit(SIGNAL('update_plugin_title()'))
531
QMessageBox.question(self, _('Trying to kill a kernel?'),
532
_("You can't close this kernel because it has one or more "
533
"consoles connected to it.<br><br>"
534
"You need to close them instead or you can kill the kernel "
535
"using the button far to the right."),
539
def set_variableexplorer(self, variableexplorer):
540
"""Set variable explorer plugin"""
541
self.variableexplorer = variableexplorer
543
def __find_python_shell(self, interpreter_only=False):
544
current_index = self.tabwidget.currentIndex()
545
if current_index == -1:
547
from spyderlib.widgets.externalshell import pythonshell
548
for index in [current_index]+range(self.tabwidget.count()):
549
shellwidget = self.tabwidget.widget(index)
550
if isinstance(shellwidget, pythonshell.ExternalPythonShell):
551
if interpreter_only and not shellwidget.is_interpreter:
553
elif not shellwidget.is_running():
556
self.tabwidget.setCurrentIndex(index)
559
def get_current_shell(self):
561
Called by object inspector to retrieve the current shell instance
563
shellwidget = self.__find_python_shell()
564
return shellwidget.shell
566
def get_running_python_shell(self):
568
Called by object inspector to retrieve a running Python shell instance
570
current_index = self.tabwidget.currentIndex()
571
if current_index == -1:
573
from spyderlib.widgets.externalshell import pythonshell
574
shellwidgets = [self.tabwidget.widget(index)
575
for index in range(self.tabwidget.count())]
576
shellwidgets = [_w for _w in shellwidgets
577
if isinstance(_w, pythonshell.ExternalPythonShell) \
580
# First, iterate on interpreters only:
581
for shellwidget in shellwidgets:
582
if shellwidget.is_interpreter:
583
return shellwidget.shell
585
return shellwidgets[0].shell
587
def run_script_in_current_shell(self, filename, wdir, args, debug):
588
"""Run script in current shell, if any"""
589
line = "%s(r'%s'" % ('debugfile' if debug else 'runfile',
591
norm = lambda text: remove_trailing_single_backslash(unicode(text))
593
line += ", args=r'%s'" % norm(args)
595
line += ", wdir=r'%s'" % norm(wdir)
597
self.execute_python_code(line, interpreter_only=True)
599
def set_current_shell_working_directory(self, directory):
600
"""Set current shell working directory"""
601
shellwidget = self.__find_python_shell()
602
if shellwidget is not None:
603
shellwidget.shell.set_cwd(unicode(directory))
605
def execute_python_code(self, lines, interpreter_only=False):
606
"""Execute Python code in an already opened Python interpreter"""
607
shellwidget = self.__find_python_shell(
608
interpreter_only=interpreter_only)
609
if shellwidget is not None:
610
if shellwidget.is_ipython_kernel:
611
# IPython Console plugin
612
ipyconsole = self.main.ipyconsole
614
# Try to send code to the currently focused client. This is
615
# necessary because several clients can be connected to the
617
ipyclient = ipyconsole.tabwidget.currentWidget()
618
if ipyclient.kernel_widget_id == id(shellwidget):
619
ipyclient.ipython_widget.execute(unicode(lines))
620
# This will send code to the first client whose kernel is
621
# shellwidget. Useful in case the user has manually changed the
624
ipw = ipyconsole.get_ipython_widget(id(shellwidget))
625
ipw.execute(unicode(lines))
628
shellwidget.shell.execute_lines(unicode(lines))
629
shellwidget.shell.setFocus()
631
def pdb_has_stopped(self, fname, lineno, shellwidget):
632
"""Python debugger has just stopped at frame (fname, lineno)"""
633
# This is a unique form of the edit_goto signal that is intended to
634
# prevent keyboard input from accidentally entering the editor
635
# during repeated, rapid entry of debugging commands.
636
self.emit(SIGNAL("edit_goto(QString,int,QString,bool)"),
637
fname, lineno, '',False)
638
if shellwidget.is_ipython_kernel:
639
# Focus client widget, not kernel
640
ipw = self.main.ipyconsole.get_focus_widget()
643
shellwidget.shell.setFocus()
645
def start(self, fname, wdir=None, args='', interact=False, debug=False,
646
python=True, ipython_kernel=False, ipython_client=False,
652
string: filename of script to run
653
None: open an interpreter
654
wdir: working directory
655
args: command line options of the Python script
656
interact: inspect script interactively after its execution
658
python: True: Python interpreter, False: terminal
659
ipython_kernel: True: IPython kernel
660
ipython_client: True: Automatically create an IPython client
661
python_args: additionnal Python interpreter command line options
662
(option "-u" is mandatory, see widgets.externalshell package)
664
# Note: fname is None <=> Python interpreter
665
if fname is not None and not isinstance(fname, basestring):
666
fname = unicode(fname)
667
if wdir is not None and not isinstance(wdir, basestring):
670
if fname is not None and fname in self.filenames:
671
index = self.filenames.index(fname)
672
if self.get_option('single_tab'):
673
old_shell = self.shellwidgets[index]
674
if old_shell.is_running():
675
answer = QMessageBox.question(self, self.get_plugin_title(),
676
_("%s is already running in a separate process.\n"
677
"Do you want to kill the process before starting "
678
"a new one?") % osp.basename(fname),
679
QMessageBox.Yes | QMessageBox.Cancel)
680
if answer == QMessageBox.Yes:
681
old_shell.process.kill()
682
old_shell.process.waitForFinished()
685
self.close_console(index)
687
index = self.tabwidget.count()
689
# Creating a new external shell
690
pythonpath = self.main.get_spyder_pythonpath()
691
light_background = self.get_option('light_background')
692
show_elapsed_time = self.get_option('show_elapsed_time')
694
pythonexecutable = self.get_option('pythonexecutable')
695
if self.get_option('pythonstartup/default', True):
698
pythonstartup = self.get_option('pythonstartup', None)
699
monitor_enabled = self.get_option('monitor/enabled')
700
mpl_patch_enabled = self.get_option('matplotlib/patch')
701
if self.get_option('matplotlib/backend/enabled'):
702
mpl_backend = self.get_option('matplotlib/backend/value')
705
ets_backend = self.get_option('ets_backend', 'qt4')
706
qt_api = self.get_option('qt/api')
707
if qt_api not in ('pyqt', 'pyside'):
709
install_qt_inputhook = self.get_option('qt/install_inputhook')
710
pyqt_api = self.get_option('pyqt/api_version', 0)
711
ignore_sip_setapi_errors = self.get_option(
712
'pyqt/ignore_sip_setapi_errors')
713
merge_output_channels = self.get_option('merge_output_channels')
714
colorize_sys_stderr = self.get_option('colorize_sys_stderr')
715
umd_enabled = self.get_option('umd/enabled')
716
umd_namelist = self.get_option('umd/namelist')
717
umd_verbose = self.get_option('umd/verbose')
718
ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout')
719
ar_state = CONF.get('variable_explorer', 'autorefresh')
721
from spyderlib.plugins.variableexplorer import VariableExplorer
722
sa_settings = VariableExplorer.get_settings()
725
shellwidget = ExternalPythonShell(self, fname, wdir,
726
interact, debug, path=pythonpath,
727
python_args=python_args,
728
ipython_kernel=ipython_kernel,
729
arguments=args, stand_alone=sa_settings,
730
pythonstartup=pythonstartup,
731
pythonexecutable=pythonexecutable,
732
umd_enabled=umd_enabled, umd_namelist=umd_namelist,
733
umd_verbose=umd_verbose, ets_backend=ets_backend,
734
monitor_enabled=monitor_enabled,
735
mpl_patch_enabled=mpl_patch_enabled,
736
mpl_backend=mpl_backend,
737
qt_api=qt_api, pyqt_api=pyqt_api,
738
install_qt_inputhook=install_qt_inputhook,
739
ignore_sip_setapi_errors=ignore_sip_setapi_errors,
740
merge_output_channels=merge_output_channels,
741
colorize_sys_stderr=colorize_sys_stderr,
742
autorefresh_timeout=ar_timeout,
743
autorefresh_state=ar_state,
744
light_background=light_background,
745
menu_actions=self.menu_actions,
746
show_buttons_inside=False,
747
show_elapsed_time=show_elapsed_time)
748
self.connect(shellwidget, SIGNAL('pdb(QString,int)'),
749
lambda fname, lineno, shellwidget=shellwidget:
750
self.pdb_has_stopped(fname, lineno, shellwidget))
751
self.register_widget_shortcuts("Console", shellwidget.shell)
753
if os.name == 'posix':
754
cmd = 'gnome-terminal'
756
if programs.is_program_installed(cmd):
758
args.extend(['--working-directory=%s' % wdir])
759
programs.run_program(cmd, args)
762
if programs.is_program_installed(cmd):
764
args.extend(['--workdir', wdir])
765
programs.run_program(cmd, args)
767
shellwidget = ExternalSystemShell(self, wdir, path=pythonpath,
768
light_background=light_background,
769
menu_actions=self.menu_actions,
770
show_buttons_inside=False,
771
show_elapsed_time=show_elapsed_time)
773
# Code completion / calltips
774
shellwidget.shell.setMaximumBlockCount(
775
self.get_option('max_line_count') )
776
shellwidget.shell.set_font( self.get_plugin_font() )
777
shellwidget.shell.toggle_wrap_mode( self.get_option('wrap') )
778
shellwidget.shell.set_calltips( self.get_option('calltips') )
779
shellwidget.shell.set_codecompletion_auto(
780
self.get_option('codecompletion/auto') )
781
shellwidget.shell.set_codecompletion_case(
782
self.get_option('codecompletion/case_sensitive') )
783
shellwidget.shell.set_codecompletion_single(
784
self.get_option('codecompletion/show_single') )
785
shellwidget.shell.set_codecompletion_enter(
786
self.get_option('codecompletion/enter_key') )
787
if python and self.inspector is not None:
788
shellwidget.shell.set_inspector(self.inspector)
789
shellwidget.shell.set_inspector_enabled(
790
self.get_option('object_inspector'))
791
if self.historylog is not None:
792
self.historylog.add_history(shellwidget.shell.history_filename)
793
self.connect(shellwidget.shell,
794
SIGNAL('append_to_history(QString,QString)'),
795
self.historylog.append_to_history)
796
self.connect(shellwidget.shell, SIGNAL("go_to_error(QString)"),
798
self.connect(shellwidget.shell, SIGNAL("focus_changed()"),
799
lambda: self.emit(SIGNAL("focus_changed()")))
801
if self.main.editor is not None:
802
self.connect(shellwidget, SIGNAL('open_file(QString,int)'),
803
self.open_file_in_spyder)
806
tab_name = _("Kernel")
807
tab_icon1 = get_icon('ipython_console.png')
808
tab_icon2 = get_icon('ipython_console_t.png')
810
self.connect(shellwidget,
811
SIGNAL('create_ipython_client(QString)'),
812
lambda cf: self.create_ipython_client(
813
cf, kernel_widget=shellwidget))
815
self.python_count += 1
816
tab_name = "Python %d" % self.python_count
817
tab_icon1 = get_icon('python.png')
818
tab_icon2 = get_icon('python_t.png')
820
tab_name = osp.basename(fname)
821
tab_icon1 = get_icon('run.png')
822
tab_icon2 = get_icon('terminated.png')
824
fname = id(shellwidget)
826
tab_name = _("Command Window")
828
tab_name = _("Terminal")
829
self.terminal_count += 1
830
tab_name += (" %d" % self.terminal_count)
831
tab_icon1 = get_icon('cmdprompt.png')
832
tab_icon2 = get_icon('cmdprompt_t.png')
833
self.shellwidgets.insert(index, shellwidget)
834
self.filenames.insert(index, fname)
835
self.icons.insert(index, (tab_icon1, tab_icon2))
837
index = self.tabwidget.addTab(shellwidget, tab_name)
839
self.tabwidget.insertTab(index, shellwidget, tab_name)
841
self.connect(shellwidget, SIGNAL("started()"),
842
lambda sid=id(shellwidget): self.process_started(sid))
843
self.connect(shellwidget, SIGNAL("finished()"),
844
lambda sid=id(shellwidget): self.process_finished(sid))
845
self.find_widget.set_editor(shellwidget.shell)
846
self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
847
self.tabwidget.setCurrentIndex(index)
848
if self.dockwidget and not self.ismaximized and not ipython_kernel:
849
self.dockwidget.setVisible(True)
850
self.dockwidget.raise_()
852
shellwidget.set_icontext_visible(self.get_option('show_icontext'))
854
# Start process and give focus to console
855
shellwidget.start_shell()
856
if not ipython_kernel:
857
shellwidget.shell.setFocus()
859
def set_ipython_kernel_attrs(self, connection_file, kernel_widget):
860
"""Add the pid of the kernel process to an IPython kernel tab"""
861
# Set connection file
862
kernel_widget.connection_file = connection_file
864
# If we've reached this point then it's safe to assume IPython
865
# is available, and this import should be valid.
866
from IPython.core.application import get_ipython_dir
867
# For each kernel we launch, setup to delete the associated
868
# connection file at the time Spyder exits.
869
def cleanup_connection_file(connection_file):
870
"""Clean up the connection file for this console at exit"""
871
connection_file = osp.join(get_ipython_dir(), 'profile_default',
872
'security', connection_file)
874
os.remove(connection_file)
877
atexit.register(cleanup_connection_file, connection_file)
880
index = self.get_shell_index_from_id(id(kernel_widget))
881
match = re.match('^kernel-(\d+).json', connection_file)
882
if match is not None: # should not fail, but we never know...
883
text = unicode(self.tabwidget.tabText(index))
884
name = "%s (%s)" % (text, match.groups()[0])
885
self.tabwidget.setTabText(index, name)
887
def create_ipython_client(self, connection_file, kernel_widget):
888
"""Create a new IPython client connected to a kernel just started"""
889
self.set_ipython_kernel_attrs(connection_file, kernel_widget)
890
self.main.ipyconsole.new_client(connection_file, id(kernel_widget))
891
# QApplication.restoreOverrideCursor() # Stop busy cursor indication
892
# QApplication.processEvents()
894
def open_file_in_spyder(self, fname, lineno):
895
"""Open file in Spyder's editor from remote process"""
896
self.main.editor.activateWindow()
897
self.main.editor.raise_()
898
self.main.editor.load(fname, lineno)
900
#------ Private API -------------------------------------------------------
901
def process_started(self, shell_id):
902
index = self.get_shell_index_from_id(shell_id)
903
shell = self.shellwidgets[index]
904
icon, _icon = self.icons[index]
905
self.tabwidget.setTabIcon(index, icon)
906
if self.inspector is not None:
907
self.inspector.set_shell(shell.shell)
908
if self.variableexplorer is not None:
909
self.variableexplorer.add_shellwidget(shell)
911
def process_finished(self, shell_id):
912
index = self.get_shell_index_from_id(shell_id)
913
if index is not None:
914
# Not sure why it happens, but sometimes the shellwidget has
915
# already been removed, so that's not bad if we can't change
917
_icon, icon = self.icons[index]
918
self.tabwidget.setTabIcon(index, icon)
919
if self.variableexplorer is not None:
920
self.variableexplorer.remove_shellwidget(shell_id)
922
#------ SpyderPluginWidget API --------------------------------------------
923
def get_plugin_title(self):
924
"""Return widget title"""
927
index = self.tabwidget.currentIndex()
928
fname = self.filenames[index]
930
title += ' - '+unicode(fname)
933
def get_plugin_icon(self):
934
"""Return widget icon"""
935
return get_icon('console.png')
937
def get_focus_widget(self):
939
Return the widget to give focus to when
940
this plugin's dockwidget is raised on top-level
942
return self.tabwidget.currentWidget()
944
def get_plugin_actions(self):
945
"""Return a list of actions related to plugin"""
946
interpreter_action = create_action(self,
947
_("Open a Python &interpreter"), None,
948
'python.png', triggered=self.open_interpreter)
950
text = _("Open &command prompt")
951
tip = _("Open a Windows command prompt")
953
text = _("Open &terminal")
954
tip = _("Open a terminal window inside Spyder")
955
terminal_action = create_action(self, text, None, 'cmdprompt.png', tip,
956
triggered=self.open_terminal)
957
run_action = create_action(self,
959
'run_small.png', _("Run a Python script"),
960
triggered=self.run_script)
962
interact_menu_actions = [interpreter_action]
963
tools_menu_actions = [terminal_action]
964
self.menu_actions = [interpreter_action, terminal_action, run_action]
966
self.ipython_kernel_action = create_action(self,
967
_("Open an IPython console"), None,
968
'ipython_console.png',
969
triggered=self.start_ipython_kernel)
970
if programs.is_module_installed('IPython.frontend.qt', '>=0.13'):
971
interact_menu_actions.append(self.ipython_kernel_action)
972
self.main.interact_menu_actions += interact_menu_actions
973
self.main.tools_menu_actions += tools_menu_actions
975
return self.menu_actions+interact_menu_actions+tools_menu_actions
977
def register_plugin(self):
978
"""Register plugin in Spyder's main window"""
980
self.main.setCentralWidget(self)
981
self.main.widgetlist.append(self)
983
self.main.add_dockwidget(self)
984
self.inspector = self.main.inspector
985
if self.inspector is not None:
986
self.inspector.set_external_console(self)
987
self.historylog = self.main.historylog
988
self.connect(self, SIGNAL("edit_goto(QString,int,QString)"),
989
self.main.editor.load)
990
self.connect(self, SIGNAL("edit_goto(QString,int,QString,bool)"),
991
lambda fname, lineno, word, processevents:
992
self.main.editor.load(fname,lineno,word,
993
processevents=processevents))
994
self.connect(self.main.editor,
995
SIGNAL('run_in_current_console(QString,QString,QString,bool)'),
996
self.run_script_in_current_shell)
997
self.connect(self.main.editor, SIGNAL("open_dir(QString)"),
998
self.set_current_shell_working_directory)
999
self.connect(self.main.workingdirectory,
1000
SIGNAL("set_current_console_wd(QString)"),
1001
self.set_current_shell_working_directory)
1002
self.connect(self, SIGNAL('focus_changed()'),
1003
self.main.plugin_focus_changed)
1004
self.connect(self, SIGNAL('redirect_stdio(bool)'),
1005
self.main.redirect_internalshell_stdio)
1006
expl = self.main.explorer
1007
if expl is not None:
1008
self.connect(expl, SIGNAL("open_terminal(QString)"),
1010
self.connect(expl, SIGNAL("open_interpreter(QString)"),
1011
self.open_interpreter)
1012
pexpl = self.main.projectexplorer
1013
if pexpl is not None:
1014
self.connect(pexpl, SIGNAL("open_terminal(QString)"),
1016
self.connect(pexpl, SIGNAL("open_interpreter(QString)"),
1017
self.open_interpreter)
1019
def closing_plugin(self, cancelable=False):
1020
"""Perform actions before parent main window is closed"""
1021
for shellwidget in self.shellwidgets:
1025
def refresh_plugin(self):
1026
"""Refresh tabwidget"""
1028
if self.tabwidget.count():
1029
shellwidget = self.tabwidget.currentWidget()
1030
editor = shellwidget.shell
1032
widgets = [shellwidget.create_time_label(), 5
1033
]+shellwidget.get_toolbar_buttons()+[5]
1037
self.find_widget.set_editor(editor)
1038
self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets})
1040
shellwidget.update_time_label_visibility()
1041
self.emit(SIGNAL('update_plugin_title()'))
1043
def apply_plugin_settings(self, options):
1044
"""Apply configuration file's plugin settings"""
1045
font = self.get_plugin_font()
1046
showtime = self.get_option('show_elapsed_time')
1047
icontext = self.get_option('show_icontext')
1048
calltips = self.get_option('calltips')
1049
inspector = self.get_option('object_inspector')
1050
wrap = self.get_option('wrap')
1051
compauto = self.get_option('codecompletion/auto')
1052
case_comp = self.get_option('codecompletion/case_sensitive')
1053
show_single = self.get_option('codecompletion/show_single')
1054
compenter = self.get_option('codecompletion/enter_key')
1055
mlc = self.get_option('max_line_count')
1056
for shellwidget in self.shellwidgets:
1057
shellwidget.shell.set_font(font)
1058
shellwidget.set_elapsed_time_visible(showtime)
1059
shellwidget.set_icontext_visible(icontext)
1060
shellwidget.shell.set_calltips(calltips)
1061
if isinstance(shellwidget.shell, ExternalPythonShell):
1062
shellwidget.shell.set_inspector_enabled(inspector)
1063
shellwidget.shell.toggle_wrap_mode(wrap)
1064
shellwidget.shell.set_codecompletion_auto(compauto)
1065
shellwidget.shell.set_codecompletion_case(case_comp)
1066
shellwidget.shell.set_codecompletion_single(show_single)
1067
shellwidget.shell.set_codecompletion_enter(compenter)
1068
shellwidget.shell.setMaximumBlockCount(mlc)
1070
#------ Public API ---------------------------------------------------------
1071
def open_interpreter_at_startup(self):
1072
"""Open an interpreter or an IPython kernel at startup"""
1073
if self.get_option('open_python_at_startup', True):
1074
self.open_interpreter()
1075
if CONF.get('ipython_console', 'open_ipython_at_startup', False):
1076
self.start_ipython_kernel()
1078
def open_interpreter(self, wdir=None):
1079
"""Open interpreter"""
1082
if not self.main.light:
1083
self.visibility_changed(True)
1084
self.start(fname=None, wdir=unicode(wdir), args='',
1085
interact=True, debug=False, python=True)
1087
def start_ipython_kernel(self, wdir=None, create_client=True):
1088
"""Start new IPython kernel"""
1089
# Add a WaitCursor visual indication, because it takes too much time
1090
# to display a new console (3 to 5 secs). It's stopped in
1091
# create_ipython_client
1092
# QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
1093
# QApplication.processEvents()
1097
self.main.ipyconsole.visibility_changed(True)
1098
self.start(fname=None, wdir=unicode(wdir), args='',
1099
interact=True, debug=False, python=True,
1100
ipython_kernel=True, ipython_client=create_client)
1102
def open_terminal(self, wdir=None):
1106
self.start(fname=None, wdir=unicode(wdir), args='',
1107
interact=True, debug=False, python=False)
1109
def run_script(self):
1110
"""Run a Python script"""
1111
self.emit(SIGNAL('redirect_stdio(bool)'), False)
1112
filename, _selfilter = getopenfilename(self, _("Run Python script"),
1113
os.getcwdu(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)")
1114
self.emit(SIGNAL('redirect_stdio(bool)'), True)
1116
self.start(fname=filename, wdir=None, args='',
1117
interact=False, debug=False)
1119
def set_umd_namelist(self):
1120
"""Set UMD excluded modules name list"""
1121
arguments, valid = QInputDialog.getText(self, _('UMD'),
1122
_('UMD excluded modules:\n'
1123
'(example: guidata, guiqwt)'),
1125
", ".join(self.get_option('umd/namelist')))
1127
arguments = unicode(arguments)
1129
namelist = arguments.replace(' ', '').split(',')
1130
fixed_namelist = [module_name for module_name in namelist
1131
if programs.is_module_installed(module_name)]
1132
invalid = ", ".join(set(namelist)-set(fixed_namelist))
1134
QMessageBox.warning(self, _('UMD'),
1135
_("The following modules are not "
1136
"installed on your machine:\n%s"
1137
) % invalid, QMessageBox.Ok)
1138
QMessageBox.information(self, _('UMD'),
1139
_("Please note that these changes will "
1140
"be applied only to new Python/IPython "
1141
"interpreters"), QMessageBox.Ok)
1144
self.set_option('umd/namelist', fixed_namelist)
1146
def go_to_error(self, text):
1147
"""Go to error if relevant"""
1148
match = get_error_match(unicode(text))
1150
fname, lnb = match.groups()
1151
self.emit(SIGNAL("edit_goto(QString,int,QString)"),
1152
osp.abspath(fname), int(lnb), '')
1155
def dragEnterEvent(self, event):
1156
"""Reimplement Qt method
1157
Inform Qt about the types of data that the widget accepts"""
1158
source = event.mimeData()
1159
if source.hasUrls():
1160
if mimedata2url(source):
1161
pathlist = mimedata2url(source)
1162
shellwidget = self.tabwidget.currentWidget()
1163
if all([is_python_script(unicode(qstr)) for qstr in pathlist]):
1164
event.acceptProposedAction()
1165
elif shellwidget is None or not shellwidget.is_running():
1168
event.acceptProposedAction()
1171
elif source.hasText():
1172
event.acceptProposedAction()
1174
def dropEvent(self, event):
1175
"""Reimplement Qt method
1176
Unpack dropped data and handle it"""
1177
source = event.mimeData()
1178
shellwidget = self.tabwidget.currentWidget()
1179
if source.hasText():
1180
qstr = source.text()
1181
if is_python_script(unicode(qstr)):
1184
shellwidget.shell.insert_text(qstr)
1185
elif source.hasUrls():
1186
pathlist = mimedata2url(source)
1187
if all([is_python_script(unicode(qstr)) for qstr in pathlist]):
1188
for fname in pathlist:
1191
shellwidget.shell.drop_pathlist(pathlist)
1192
event.acceptProposedAction()
1
# -*- coding: utf-8 -*-
3
# Copyright © 2009-2010 Pierre Raybaut
4
# Licensed under the terms of the MIT License
5
# (see spyderlib/__init__.py for details)
7
"""External Console plugin"""
9
# pylint: disable=C0103
10
# pylint: disable=R0903
11
# pylint: disable=R0911
12
# pylint: disable=R0201
14
from spyderlib.qt.QtGui import (QVBoxLayout, QMessageBox, QInputDialog,
15
QLineEdit, QPushButton, QGroupBox, QLabel,
16
QTabWidget, QFontComboBox, QHBoxLayout)
17
from spyderlib.qt.QtCore import SIGNAL, Qt
18
from spyderlib.qt.compat import getopenfilename
28
from spyderlib.baseconfig import _, SCIENTIFIC_STARTUP
29
from spyderlib.config import CONF
30
from spyderlib.utils import programs
31
from spyderlib.utils.misc import (get_error_match, get_python_executable,
32
remove_trailing_single_backslash,
34
from spyderlib.utils.qthelpers import get_icon, create_action, mimedata2url
35
from spyderlib.widgets.tabs import Tabs
36
from spyderlib.widgets.externalshell.pythonshell import ExternalPythonShell
37
from spyderlib.widgets.externalshell.systemshell import ExternalSystemShell
38
from spyderlib.widgets.findreplace import FindReplace
39
from spyderlib.plugins import SpyderPluginWidget, PluginConfigPage
42
def is_mpl_patch_available():
43
"""Return True if Matplotlib patch is available"""
44
if programs.is_module_installed('matplotlib'):
45
from spyderlib import mpl_patch
46
return mpl_patch.is_available()
51
class ExternalConsoleConfigPage(PluginConfigPage):
52
def __init__(self, plugin, parent):
53
PluginConfigPage.__init__(self, plugin, parent)
54
self.get_name = lambda: _("Console")
57
interface_group = QGroupBox(_("Interface"))
58
font_group = self.create_fontgroup(option=None, text=None,
59
fontfilters=QFontComboBox.MonospacedFonts)
60
newcb = self.create_checkbox
61
singletab_box = newcb(_("One tab per script"), 'single_tab')
62
showtime_box = newcb(_("Show elapsed time"), 'show_elapsed_time')
63
icontext_box = newcb(_("Show icons and text"), 'show_icontext')
66
interface_layout = QVBoxLayout()
67
interface_layout.addWidget(singletab_box)
68
interface_layout.addWidget(showtime_box)
69
interface_layout.addWidget(icontext_box)
70
interface_group.setLayout(interface_layout)
73
display_group = QGroupBox(_("Source code"))
74
buffer_spin = self.create_spinbox(
75
_("Buffer: "), _(" lines"),
76
'max_line_count', min_=0, max_=1000000, step=100,
77
tip=_("Set maximum line count"))
78
wrap_mode_box = newcb(_("Wrap lines"), 'wrap')
79
merge_channels_box = newcb(
80
_("Merge process standard output/error channels"),
81
'merge_output_channels',
82
tip=_("Merging the output channels of the process means that\n"
83
"the standard error won't be written in red anymore,\n"
84
"but this has the effect of speeding up display."))
85
colorize_sys_stderr_box = newcb(
86
_("Colorize standard error channel using ANSI escape codes"),
87
'colorize_sys_stderr',
88
tip=_("This method is the only way to have colorized standard\n"
89
"error channel when the output channels have been "
91
self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
92
colorize_sys_stderr_box.setEnabled)
93
self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
94
colorize_sys_stderr_box.setChecked)
95
colorize_sys_stderr_box.setEnabled(
96
self.get_option('merge_output_channels'))
98
display_layout = QVBoxLayout()
99
display_layout.addWidget(buffer_spin)
100
display_layout.addWidget(wrap_mode_box)
101
display_layout.addWidget(merge_channels_box)
102
display_layout.addWidget(colorize_sys_stderr_box)
103
display_group.setLayout(display_layout)
105
# Background Color Group
106
bg_group = QGroupBox(_("Background color"))
107
bg_label = QLabel(_("This option will be applied the next time "
108
"a Python console or a terminal is opened."))
109
bg_label.setWordWrap(True)
110
lightbg_box = newcb(_("Light background (white color)"),
112
bg_layout = QVBoxLayout()
113
bg_layout.addWidget(bg_label)
114
bg_layout.addWidget(lightbg_box)
115
bg_group.setLayout(bg_layout)
118
source_group = QGroupBox(_("Source code"))
119
completion_box = newcb(_("Automatic code completion"),
120
'codecompletion/auto')
121
case_comp_box = newcb(_("Case sensitive code completion"),
122
'codecompletion/case_sensitive')
123
show_single_box = newcb(_("Show single completion"),
124
'codecompletion/show_single')
125
comp_enter_box = newcb(_("Enter key selects completion"),
126
'codecompletion/enter_key')
127
calltips_box = newcb(_("Balloon tips"), 'calltips')
128
inspector_box = newcb(
129
_("Automatic notification to object inspector"),
130
'object_inspector', default=True,
131
tip=_("If this option is enabled, object inspector\n"
132
"will automatically show informations on functions\n"
133
"entered in console (this is triggered when entering\n"
134
"a left parenthesis after a valid function name)"))
136
source_layout = QVBoxLayout()
137
source_layout.addWidget(completion_box)
138
source_layout.addWidget(case_comp_box)
139
source_layout.addWidget(show_single_box)
140
source_layout.addWidget(comp_enter_box)
141
source_layout.addWidget(calltips_box)
142
source_layout.addWidget(inspector_box)
143
source_group.setLayout(source_layout)
146
umd_group = QGroupBox(_("User Module Deleter (UMD)"))
147
umd_label = QLabel(_("UMD forces Python to reload modules which were "
148
"imported when executing a \nscript in the "
149
"external console with the 'runfile' function."))
150
umd_enabled_box = newcb(_("Enable UMD"), 'umd/enabled',
151
msg_if_enabled=True, msg_warning=_(
152
"This option will enable the User Module Deleter (UMD) "
153
"in Python interpreters. UMD forces Python to "
154
"reload deeply modules during import when running a "
155
"Python script using the Spyder's builtin function "
157
"<br><br><b>1.</b> UMD may require to restart the "
158
"Python interpreter in which it will be called "
159
"(otherwise only newly imported modules will be "
160
"reloaded when executing scripts)."
161
"<br><br><b>2.</b> If errors occur when re-running a "
162
"PyQt-based program, please check that the Qt objects "
163
"are properly destroyed (e.g. you may have to use the "
164
"attribute <b>Qt.WA_DeleteOnClose</b> on your main "
165
"window, using the <b>setAttribute</b> method)"),
167
umd_verbose_box = newcb(_("Show reloaded modules list"),
168
'umd/verbose', msg_info=_(
169
"Please note that these changes will "
170
"be applied only to new Python interpreters"))
171
umd_namelist_btn = QPushButton(
172
_("Set UMD excluded (not reloaded) modules"))
173
self.connect(umd_namelist_btn, SIGNAL('clicked()'),
174
self.plugin.set_umd_namelist)
176
umd_layout = QVBoxLayout()
177
umd_layout.addWidget(umd_label)
178
umd_layout.addWidget(umd_enabled_box)
179
umd_layout.addWidget(umd_verbose_box)
180
umd_layout.addWidget(umd_namelist_btn)
181
umd_group.setLayout(umd_layout)
183
# Python executable Group
184
pyexec_group = QGroupBox(_("Python executable"))
185
pyexec_label = QLabel(_("Path to Python interpreter "
186
"executable binary:"))
188
filters = _("Executables")+" (*.exe)"
191
pyexec_file = self.create_browsefile('', 'pythonexecutable',
194
pyexec_layout = QVBoxLayout()
195
pyexec_layout.addWidget(pyexec_label)
196
pyexec_layout.addWidget(pyexec_file)
197
pyexec_group.setLayout(pyexec_layout)
200
startup_group = QGroupBox(_("Startup"))
201
pystartup_box = newcb(_("Open a Python interpreter at startup"),
202
'open_python_at_startup')
204
startup_layout = QVBoxLayout()
205
startup_layout.addWidget(pystartup_box)
206
startup_group.setLayout(startup_layout)
208
# PYTHONSTARTUP replacement
209
pystartup_group = QGroupBox(_("PYTHONSTARTUP replacement"))
210
pystartup_label = QLabel(_("This option will override the "
211
"PYTHONSTARTUP environment variable which\n"
212
"defines the script to be executed during "
213
"the Python interpreter startup."))
214
default_radio = self.create_radiobutton(
215
_("Default PYTHONSTARTUP script"),
216
'pythonstartup/default', True)
217
custom_radio = self.create_radiobutton(
218
_("Use the following startup script:"),
219
'pythonstartup/custom', False)
220
pystartup_file = self.create_browsefile('', 'pythonstartup', '',
221
filters=_("Python scripts")+\
223
self.connect(default_radio, SIGNAL("toggled(bool)"),
224
pystartup_file.setDisabled)
225
self.connect(custom_radio, SIGNAL("toggled(bool)"),
226
pystartup_file.setEnabled)
228
pystartup_layout = QVBoxLayout()
229
pystartup_layout.addWidget(pystartup_label)
230
pystartup_layout.addWidget(default_radio)
231
pystartup_layout.addWidget(custom_radio)
232
pystartup_layout.addWidget(pystartup_file)
233
pystartup_group.setLayout(pystartup_layout)
236
monitor_group = QGroupBox(_("Monitor"))
237
monitor_label = QLabel(_("The monitor provides introspection "
238
"features to console: code completion, "
239
"calltips and variable explorer. "
240
"Because it relies on several modules, "
241
"disabling the monitor may be useful "
242
"to accelerate console startup."))
243
monitor_label.setWordWrap(True)
244
monitor_box = newcb(_("Enable monitor"), 'monitor/enabled')
245
for obj in (completion_box, case_comp_box, show_single_box,
246
comp_enter_box, calltips_box):
247
self.connect(monitor_box, SIGNAL("toggled(bool)"), obj.setEnabled)
248
obj.setEnabled(self.get_option('monitor/enabled'))
250
monitor_layout = QVBoxLayout()
251
monitor_layout.addWidget(monitor_label)
252
monitor_layout.addWidget(monitor_box)
253
monitor_group.setLayout(monitor_layout)
256
# Do not test if PyQt4 or PySide is installed with the function
257
# spyderlib.utils.programs.is_module_installed because it will
258
# fail (both libraries can't be imported at the same time):
260
imp.find_module('PyQt4')
265
imp.find_module('PySide')
271
opts.append( ('PyQt4', 'pyqt') )
273
opts.append( ('PySide', 'pyside') )
274
qt_group = QGroupBox(_("Qt (PyQt/PySide)"))
275
qt_setapi_box = self.create_combobox(
276
_("Qt-Python bindings library selection:"),
277
[(_("Default library"), 'default')]+opts,
278
'qt/api', default='default', tip=_(
279
"""This option will act on libraries such as Matplotlib, guidata or ETS"""))
280
qt_hook_box = newcb(_("Install Spyder's input hook for Qt"),
281
'qt/install_inputhook',
283
"""PyQt installs an input hook that allows creating and interacting
284
with Qt widgets in an interactive interpreter without blocking it.
285
On Windows platforms, it is strongly recommended to replace it by Spyder's.
286
Regarding PySide, note that it does not install an input hook, so it is
287
required to enable this feature in order to be able to manipulate PySide/Qt
288
objects interactively."""))
290
qt_layout = QVBoxLayout()
291
qt_layout.addWidget(qt_setapi_box)
292
qt_layout.addWidget(qt_hook_box)
293
qt_group.setLayout(qt_layout)
294
qt_group.setEnabled(has_pyqt4 or has_pyside)
298
pyqt_group = QGroupBox(_("PyQt"))
299
setapi_box = self.create_combobox(
300
_("API selection for QString and QVariant objects:"),
301
((_("Default API"), 0), (_("API #1"), 1), (_("API #2"), 2)),
302
'pyqt/api_version', default=0, tip=_(
303
"""PyQt API #1 is the default API for Python 2. PyQt API #2 is the default
304
API for Python 3 and is compatible with PySide.
305
Note that switching to API #2 may require to enable the Matplotlib patch."""))
306
ignore_api_box = newcb(_("Ignore API change errors (sip.setapi)"),
307
'pyqt/ignore_sip_setapi_errors', tip=_(
308
"""Enabling this option will ignore errors when changing PyQt API.
309
As PyQt does not support dynamic API changes, it is strongly recommended
310
to use this feature wisely, e.g. for debugging purpose."""))
312
from sip import setapi #analysis:ignore
314
setapi_box.setDisabled(True)
315
ignore_api_box.setDisabled(True)
317
pyqt_layout = QVBoxLayout()
318
pyqt_layout.addWidget(setapi_box)
319
pyqt_layout.addWidget(ignore_api_box)
320
pyqt_group.setLayout(pyqt_layout)
321
qt_layout.addWidget(pyqt_group)
324
mpl_group = QGroupBox(_("Matplotlib"))
325
mpl_backend_box = newcb('', 'matplotlib/backend/enabled', True)
326
mpl_backend_edit = self.create_lineedit(_("GUI backend:"),
327
'matplotlib/backend/value', "Qt4Agg",
328
_("Set the GUI toolkit used by Matplotlib to "
329
"show figures (default: Qt4Agg)"),
330
alignment=Qt.Horizontal)
331
self.connect(mpl_backend_box, SIGNAL("toggled(bool)"),
332
mpl_backend_edit.setEnabled)
333
mpl_backend_layout = QHBoxLayout()
334
mpl_backend_layout.addWidget(mpl_backend_box)
335
mpl_backend_layout.addWidget(mpl_backend_edit)
336
mpl_backend_edit.setEnabled(
337
self.get_option('matplotlib/backend/enabled'))
338
mpl_patch_box = newcb(_("Patch Matplotlib figures"),
339
'matplotlib/patch', False)
340
mpl_patch_label = QLabel(_("Patching Matplotlib library will add a "
341
"button to customize figure options "
342
"(Qt4Agg only) and fix some issues."))
343
mpl_patch_label.setWordWrap(True)
344
self.connect(mpl_patch_box, SIGNAL("toggled(bool)"),
345
mpl_patch_label.setEnabled)
347
mpl_installed = programs.is_module_installed('matplotlib')
348
if not is_mpl_patch_available():
350
mpl_patch_label.hide()
352
mpl_layout = QVBoxLayout()
353
mpl_layout.addLayout(mpl_backend_layout)
354
mpl_layout.addWidget(mpl_patch_box)
355
mpl_layout.addWidget(mpl_patch_label)
356
mpl_group.setLayout(mpl_layout)
357
mpl_group.setEnabled(mpl_installed)
360
ets_group = QGroupBox(_("Enthought Tool Suite"))
361
ets_label = QLabel(_("Enthought Tool Suite (ETS) supports "
362
"PyQt4 (qt4) and wxPython (wx) graphical "
364
ets_label.setWordWrap(True)
365
ets_edit = self.create_lineedit(_("ETS_TOOLKIT:"), 'ets_backend',
366
default='qt4', alignment=Qt.Horizontal)
368
ets_layout = QVBoxLayout()
369
ets_layout.addWidget(ets_label)
370
ets_layout.addWidget(ets_edit)
371
ets_group.setLayout(ets_layout)
372
ets_group.setEnabled(programs.is_module_installed(
373
"enthought.etsconfig.api"))
376
tabs.addTab(self.create_tab(font_group, interface_group, display_group,
379
tabs.addTab(self.create_tab(monitor_group, source_group),
381
tabs.addTab(self.create_tab(pyexec_group, startup_group,
382
pystartup_group, umd_group),
383
_("Advanced settings"))
384
tabs.addTab(self.create_tab(qt_group, mpl_group, ets_group),
385
_("External modules"))
387
vlayout = QVBoxLayout()
388
vlayout.addWidget(tabs)
389
self.setLayout(vlayout)
392
class ExternalConsole(SpyderPluginWidget):
396
CONF_SECTION = 'console'
397
CONFIGWIDGET_CLASS = ExternalConsoleConfigPage
398
def __init__(self, parent, light_mode):
399
SpyderPluginWidget.__init__(self, parent)
400
self.light_mode = light_mode
401
self.tabwidget = None
402
self.menu_actions = None
404
self.inspector = None # Object inspector plugin
405
self.historylog = None # History log plugin
406
self.variableexplorer = None # Variable explorer plugin
408
self.python_count = 0
409
self.terminal_count = 0
412
from sip import setapi #analysis:ignore
414
self.set_option('pyqt/ignore_sip_setapi_errors', False)
416
scientific = programs.is_module_installed('numpy') and\
417
programs.is_module_installed('scipy') and\
418
programs.is_module_installed('matplotlib')
419
if self.get_option('pythonstartup/default', None) is None:
420
self.set_option('pythonstartup/default', not scientific)
421
if not osp.isfile(self.get_option('pythonstartup', '')):
422
self.set_option('pythonstartup', SCIENTIFIC_STARTUP)
423
self.set_option('pythonstartup/default', not scientific)
424
# default/custom settings are mutually exclusive:
425
self.set_option('pythonstartup/custom',
426
not self.get_option('pythonstartup/default'))
428
executable = self.get_option('pythonexecutable',
429
get_python_executable())
430
if not osp.isfile(executable):
431
# This is absolutely necessary, in case the Python interpreter
432
# executable has been moved since last Spyder execution (following
433
# a Python distribution upgrade for example)
434
self.set_option('pythonexecutable', get_python_executable())
435
elif executable.endswith('pythonw.exe'):
436
# That should not be necessary because this case is already taken
437
# care of by the `get_python_executable` function but, this was
438
# implemented too late, so we have to fix it here too, in case
439
# the Python executable has already been set with pythonw.exe:
440
self.set_option('pythonexecutable',
441
executable.replace("pythonw.exe", "python.exe"))
443
self.shellwidgets = []
446
self.runfile_args = ""
449
self.initialize_plugin()
451
layout = QVBoxLayout()
452
self.tabwidget = Tabs(self, self.menu_actions)
453
if hasattr(self.tabwidget, 'setDocumentMode')\
454
and not sys.platform == 'darwin':
455
# Don't set document mode to true on OSX because it generates
456
# a crash when the console is detached from the main window
458
self.tabwidget.setDocumentMode(True)
459
self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
461
self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
463
self.connect(self.main, SIGNAL("pythonpath_changed()"),
466
self.tabwidget.set_close_function(self.close_console)
468
layout.addWidget(self.tabwidget)
470
# Find/replace widget
471
self.find_widget = FindReplace(self)
472
self.find_widget.hide()
473
self.register_widget_shortcuts("Editor", self.find_widget)
475
layout.addWidget(self.find_widget)
477
self.setLayout(layout)
480
self.setAcceptDrops(True)
482
def move_tab(self, index_from, index_to):
484
Move tab (tabs themselves have already been moved by the tabwidget)
486
filename = self.filenames.pop(index_from)
487
shell = self.shellwidgets.pop(index_from)
488
icons = self.icons.pop(index_from)
490
self.filenames.insert(index_to, filename)
491
self.shellwidgets.insert(index_to, shell)
492
self.icons.insert(index_to, icons)
493
self.emit(SIGNAL('update_plugin_title()'))
495
def get_shell_index_from_id(self, shell_id):
496
"""Return shellwidget index from id"""
497
for index, shell in enumerate(self.shellwidgets):
498
if id(shell) == shell_id:
501
def close_console(self, index=None, from_ipyclient=False):
502
"""Close console tab from index or widget (or close current tab)"""
504
if not self.tabwidget.count():
507
index = self.tabwidget.currentIndex()
509
# Detect what widget we are trying to close
510
for i, s in enumerate(self.shellwidgets):
514
# If the tab is an IPython kernel, try to detect if it has a client
516
if shellwidget.is_ipykernel:
517
ipyclients = self.main.ipyconsole.get_clients()
519
for ic in ipyclients:
520
if ic.kernel_widget_id == id(shellwidget):
521
connected_ipyclient = True
524
connected_ipyclient = False
526
connected_ipyclient = False
529
if not shellwidget.is_ipykernel or from_ipyclient or \
530
not connected_ipyclient:
531
self.tabwidget.widget(index).close()
532
self.tabwidget.removeTab(index)
533
self.filenames.pop(index)
534
self.shellwidgets.pop(index)
535
self.icons.pop(index)
536
self.emit(SIGNAL('update_plugin_title()'))
538
QMessageBox.question(self, _('Trying to kill a kernel?'),
539
_("You can't close this kernel because it has one or more "
540
"consoles connected to it.<br><br>"
541
"You need to close them instead or you can kill the kernel "
542
"using the button far to the right."),
546
def set_variableexplorer(self, variableexplorer):
547
"""Set variable explorer plugin"""
548
self.variableexplorer = variableexplorer
551
"""Set consoles PYTHONPATH if changed by the user"""
552
from spyderlib.widgets.externalshell import pythonshell
553
for sw in self.shellwidgets:
554
if isinstance(sw, pythonshell.ExternalPythonShell):
555
if sw.is_interpreter and sw.is_running():
556
sw.path = self.main.get_spyder_pythonpath()
557
sw.shell.path = sw.path
559
def __find_python_shell(self, interpreter_only=False):
560
current_index = self.tabwidget.currentIndex()
561
if current_index == -1:
563
from spyderlib.widgets.externalshell import pythonshell
564
for index in [current_index]+range(self.tabwidget.count()):
565
shellwidget = self.tabwidget.widget(index)
566
if isinstance(shellwidget, pythonshell.ExternalPythonShell):
567
if interpreter_only and not shellwidget.is_interpreter:
569
elif not shellwidget.is_running():
572
self.tabwidget.setCurrentIndex(index)
575
def get_current_shell(self):
577
Called by object inspector to retrieve the current shell instance
579
shellwidget = self.__find_python_shell()
580
return shellwidget.shell
582
def get_running_python_shell(self):
584
Called by object inspector to retrieve a running Python shell instance
586
current_index = self.tabwidget.currentIndex()
587
if current_index == -1:
589
from spyderlib.widgets.externalshell import pythonshell
590
shellwidgets = [self.tabwidget.widget(index)
591
for index in range(self.tabwidget.count())]
592
shellwidgets = [_w for _w in shellwidgets
593
if isinstance(_w, pythonshell.ExternalPythonShell) \
596
# First, iterate on interpreters only:
597
for shellwidget in shellwidgets:
598
if shellwidget.is_interpreter:
599
return shellwidget.shell
601
return shellwidgets[0].shell
603
def run_script_in_current_shell(self, filename, wdir, args, debug):
604
"""Run script in current shell, if any"""
605
line = "%s(r'%s'" % ('debugfile' if debug else 'runfile',
607
norm = lambda text: remove_trailing_single_backslash(unicode(text))
609
line += ", args=r'%s'" % norm(args)
611
line += ", wdir=r'%s'" % norm(wdir)
613
self.execute_python_code(line, interpreter_only=True)
615
def set_current_shell_working_directory(self, directory):
616
"""Set current shell working directory"""
617
shellwidget = self.__find_python_shell()
618
if shellwidget is not None:
619
shellwidget.shell.set_cwd(unicode(directory))
621
def execute_python_code(self, lines, interpreter_only=False):
622
"""Execute Python code in an already opened Python interpreter"""
623
shellwidget = self.__find_python_shell(
624
interpreter_only=interpreter_only)
625
if (shellwidget is not None) and (not shellwidget.is_ipykernel):
626
shellwidget.shell.execute_lines(unicode(lines))
627
self.activateWindow()
628
shellwidget.shell.setFocus()
630
def pdb_has_stopped(self, fname, lineno, shellwidget):
631
"""Python debugger has just stopped at frame (fname, lineno)"""
632
# This is a unique form of the edit_goto signal that is intended to
633
# prevent keyboard input from accidentally entering the editor
634
# during repeated, rapid entry of debugging commands.
635
self.emit(SIGNAL("edit_goto(QString,int,QString,bool)"),
636
fname, lineno, '',False)
637
if shellwidget.is_ipykernel:
638
# Focus client widget, not kernel
639
ipw = self.main.ipyconsole.get_focus_widget()
640
self.main.ipyconsole.activateWindow()
643
self.activateWindow()
644
shellwidget.shell.setFocus()
646
def start(self, fname, wdir=None, args='', interact=False, debug=False,
647
python=True, ipykernel=False, ipyclient=False,
653
string: filename of script to run
654
None: open an interpreter
655
wdir: working directory
656
args: command line options of the Python script
657
interact: inspect script interactively after its execution
659
python: True: Python interpreter, False: terminal
660
ipykernel: True: IPython kernel
661
ipyclient: True: Automatically create an IPython client
662
python_args: additionnal Python interpreter command line options
663
(option "-u" is mandatory, see widgets.externalshell package)
665
# Note: fname is None <=> Python interpreter
666
if fname is not None and not isinstance(fname, basestring):
667
fname = unicode(fname)
668
if wdir is not None and not isinstance(wdir, basestring):
671
if fname is not None and fname in self.filenames:
672
index = self.filenames.index(fname)
673
if self.get_option('single_tab'):
674
old_shell = self.shellwidgets[index]
675
if old_shell.is_running():
676
answer = QMessageBox.question(self, self.get_plugin_title(),
677
_("%s is already running in a separate process.\n"
678
"Do you want to kill the process before starting "
679
"a new one?") % osp.basename(fname),
680
QMessageBox.Yes | QMessageBox.Cancel)
681
if answer == QMessageBox.Yes:
682
old_shell.process.kill()
683
old_shell.process.waitForFinished()
686
self.close_console(index)
688
index = self.tabwidget.count()
690
# Creating a new external shell
691
pythonpath = self.main.get_spyder_pythonpath()
692
light_background = self.get_option('light_background')
693
show_elapsed_time = self.get_option('show_elapsed_time')
695
pythonexecutable = self.get_option('pythonexecutable')
696
if self.get_option('pythonstartup/default', True):
699
pythonstartup = self.get_option('pythonstartup', None)
700
monitor_enabled = self.get_option('monitor/enabled')
701
mpl_patch_enabled = is_mpl_patch_available() and\
702
self.get_option('matplotlib/patch')
703
if self.get_option('matplotlib/backend/enabled'):
704
mpl_backend = self.get_option('matplotlib/backend/value')
707
ets_backend = self.get_option('ets_backend', 'qt4')
708
qt_api = self.get_option('qt/api')
709
if qt_api not in ('pyqt', 'pyside'):
711
install_qt_inputhook = self.get_option('qt/install_inputhook')
712
pyqt_api = self.get_option('pyqt/api_version', 0)
713
ignore_sip_setapi_errors = self.get_option(
714
'pyqt/ignore_sip_setapi_errors')
715
merge_output_channels = self.get_option('merge_output_channels')
716
colorize_sys_stderr = self.get_option('colorize_sys_stderr')
717
umd_enabled = self.get_option('umd/enabled')
718
umd_namelist = self.get_option('umd/namelist')
719
umd_verbose = self.get_option('umd/verbose')
720
ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout')
721
ar_state = CONF.get('variable_explorer', 'autorefresh')
723
from spyderlib.plugins.variableexplorer import VariableExplorer
724
sa_settings = VariableExplorer.get_settings()
727
shellwidget = ExternalPythonShell(self, fname, wdir,
728
interact, debug, path=pythonpath,
729
python_args=python_args,
731
arguments=args, stand_alone=sa_settings,
732
pythonstartup=pythonstartup,
733
pythonexecutable=pythonexecutable,
734
umd_enabled=umd_enabled, umd_namelist=umd_namelist,
735
umd_verbose=umd_verbose, ets_backend=ets_backend,
736
monitor_enabled=monitor_enabled,
737
mpl_patch_enabled=mpl_patch_enabled,
738
mpl_backend=mpl_backend,
739
qt_api=qt_api, pyqt_api=pyqt_api,
740
install_qt_inputhook=install_qt_inputhook,
741
ignore_sip_setapi_errors=ignore_sip_setapi_errors,
742
merge_output_channels=merge_output_channels,
743
colorize_sys_stderr=colorize_sys_stderr,
744
autorefresh_timeout=ar_timeout,
745
autorefresh_state=ar_state,
746
light_background=light_background,
747
menu_actions=self.menu_actions,
748
show_buttons_inside=False,
749
show_elapsed_time=show_elapsed_time)
750
self.connect(shellwidget, SIGNAL('pdb(QString,int)'),
751
lambda fname, lineno, shellwidget=shellwidget:
752
self.pdb_has_stopped(fname, lineno, shellwidget))
753
self.register_widget_shortcuts("Console", shellwidget.shell)
755
if os.name == 'posix':
756
cmd = 'gnome-terminal'
758
if programs.is_program_installed(cmd):
760
args.extend(['--working-directory=%s' % wdir])
761
programs.run_program(cmd, args)
764
if programs.is_program_installed(cmd):
766
args.extend(['--workdir', wdir])
767
programs.run_program(cmd, args)
769
shellwidget = ExternalSystemShell(self, wdir, path=pythonpath,
770
light_background=light_background,
771
menu_actions=self.menu_actions,
772
show_buttons_inside=False,
773
show_elapsed_time=show_elapsed_time)
775
# Code completion / calltips
776
shellwidget.shell.setMaximumBlockCount(
777
self.get_option('max_line_count') )
778
shellwidget.shell.set_font( self.get_plugin_font() )
779
shellwidget.shell.toggle_wrap_mode( self.get_option('wrap') )
780
shellwidget.shell.set_calltips( self.get_option('calltips') )
781
shellwidget.shell.set_codecompletion_auto(
782
self.get_option('codecompletion/auto') )
783
shellwidget.shell.set_codecompletion_case(
784
self.get_option('codecompletion/case_sensitive') )
785
shellwidget.shell.set_codecompletion_single(
786
self.get_option('codecompletion/show_single') )
787
shellwidget.shell.set_codecompletion_enter(
788
self.get_option('codecompletion/enter_key') )
789
if python and self.inspector is not None:
790
shellwidget.shell.set_inspector(self.inspector)
791
shellwidget.shell.set_inspector_enabled(
792
self.get_option('object_inspector'))
793
if self.historylog is not None:
794
self.historylog.add_history(shellwidget.shell.history_filename)
795
self.connect(shellwidget.shell,
796
SIGNAL('append_to_history(QString,QString)'),
797
self.historylog.append_to_history)
798
self.connect(shellwidget.shell, SIGNAL("go_to_error(QString)"),
800
self.connect(shellwidget.shell, SIGNAL("focus_changed()"),
801
lambda: self.emit(SIGNAL("focus_changed()")))
803
if self.main.editor is not None:
804
self.connect(shellwidget, SIGNAL('open_file(QString,int)'),
805
self.open_file_in_spyder)
808
tab_name = _("Kernel")
809
tab_icon1 = get_icon('ipython_console.png')
810
tab_icon2 = get_icon('ipython_console_t.png')
812
self.connect(shellwidget,
813
SIGNAL('create_ipython_client(QString)'),
814
lambda cf: self.create_ipyclient(
815
cf, kernel_widget=shellwidget))
817
self.python_count += 1
818
tab_name = "Python %d" % self.python_count
819
tab_icon1 = get_icon('python.png')
820
tab_icon2 = get_icon('python_t.png')
822
tab_name = osp.basename(fname)
823
tab_icon1 = get_icon('run.png')
824
tab_icon2 = get_icon('terminated.png')
826
fname = id(shellwidget)
828
tab_name = _("Command Window")
830
tab_name = _("Terminal")
831
self.terminal_count += 1
832
tab_name += (" %d" % self.terminal_count)
833
tab_icon1 = get_icon('cmdprompt.png')
834
tab_icon2 = get_icon('cmdprompt_t.png')
835
self.shellwidgets.insert(index, shellwidget)
836
self.filenames.insert(index, fname)
837
self.icons.insert(index, (tab_icon1, tab_icon2))
839
index = self.tabwidget.addTab(shellwidget, tab_name)
841
self.tabwidget.insertTab(index, shellwidget, tab_name)
843
self.connect(shellwidget, SIGNAL("started()"),
844
lambda sid=id(shellwidget): self.process_started(sid))
845
self.connect(shellwidget, SIGNAL("finished()"),
846
lambda sid=id(shellwidget): self.process_finished(sid))
847
self.find_widget.set_editor(shellwidget.shell)
848
self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
849
self.tabwidget.setCurrentIndex(index)
850
if self.dockwidget and not self.ismaximized and not ipykernel:
851
self.dockwidget.setVisible(True)
852
self.dockwidget.raise_()
854
shellwidget.set_icontext_visible(self.get_option('show_icontext'))
856
# Start process and give focus to console
857
shellwidget.start_shell()
859
self.activateWindow()
860
shellwidget.shell.setFocus()
862
def set_ipykernel_attrs(self, connection_file, kernel_widget):
863
"""Add the pid of the kernel process to an IPython kernel tab"""
864
# Set connection file
865
kernel_widget.connection_file = connection_file
867
# If we've reached this point then it's safe to assume IPython
868
# is available, and this import should be valid.
869
from IPython.core.application import get_ipython_dir
870
# For each kernel we launch, setup to delete the associated
871
# connection file at the time Spyder exits.
872
def cleanup_connection_file(connection_file):
873
"""Clean up the connection file for this console at exit"""
874
connection_file = osp.join(get_ipython_dir(), 'profile_default',
875
'security', connection_file)
877
os.remove(connection_file)
880
atexit.register(cleanup_connection_file, connection_file)
883
index = self.get_shell_index_from_id(id(kernel_widget))
884
match = re.match('^kernel-(\d+).json', connection_file)
885
if match is not None: # should not fail, but we never know...
886
text = unicode(self.tabwidget.tabText(index))
887
name = "%s %s" % (text, match.groups()[0])
888
self.tabwidget.setTabText(index, name)
890
def create_ipyclient(self, connection_file, kernel_widget):
891
"""Create a new IPython client connected to a kernel just started"""
892
self.set_ipykernel_attrs(connection_file, kernel_widget)
893
self.main.ipyconsole.new_client(connection_file, id(kernel_widget))
894
# QApplication.restoreOverrideCursor() # Stop busy cursor indication
895
# QApplication.processEvents()
897
def open_file_in_spyder(self, fname, lineno):
898
"""Open file in Spyder's editor from remote process"""
899
self.main.editor.activateWindow()
900
self.main.editor.raise_()
901
self.main.editor.load(fname, lineno)
903
#------ Private API -------------------------------------------------------
904
def process_started(self, shell_id):
905
index = self.get_shell_index_from_id(shell_id)
906
shell = self.shellwidgets[index]
907
icon, _icon = self.icons[index]
908
self.tabwidget.setTabIcon(index, icon)
909
if self.inspector is not None:
910
self.inspector.set_shell(shell.shell)
911
if self.variableexplorer is not None:
912
self.variableexplorer.add_shellwidget(shell)
914
def process_finished(self, shell_id):
915
index = self.get_shell_index_from_id(shell_id)
916
if index is not None:
917
# Not sure why it happens, but sometimes the shellwidget has
918
# already been removed, so that's not bad if we can't change
920
_icon, icon = self.icons[index]
921
self.tabwidget.setTabIcon(index, icon)
922
if self.variableexplorer is not None:
923
self.variableexplorer.remove_shellwidget(shell_id)
925
#------ SpyderPluginWidget API --------------------------------------------
926
def get_plugin_title(self):
927
"""Return widget title"""
930
index = self.tabwidget.currentIndex()
931
fname = self.filenames[index]
933
title += ' - '+unicode(fname)
936
def get_plugin_icon(self):
937
"""Return widget icon"""
938
return get_icon('console.png')
940
def get_focus_widget(self):
942
Return the widget to give focus to when
943
this plugin's dockwidget is raised on top-level
945
return self.tabwidget.currentWidget()
947
def get_plugin_actions(self):
948
"""Return a list of actions related to plugin"""
949
interpreter_action = create_action(self,
950
_("Open a Python &interpreter"), None,
951
'python.png', triggered=self.open_interpreter)
953
text = _("Open &command prompt")
954
tip = _("Open a Windows command prompt")
956
text = _("Open &terminal")
957
tip = _("Open a terminal window inside Spyder")
958
terminal_action = create_action(self, text, None, 'cmdprompt.png', tip,
959
triggered=self.open_terminal)
960
run_action = create_action(self,
962
'run_small.png', _("Run a Python script"),
963
triggered=self.run_script)
965
interact_menu_actions = [interpreter_action]
966
tools_menu_actions = [terminal_action]
967
self.menu_actions = [interpreter_action, terminal_action, run_action]
969
self.ipykernel_action = create_action(self,
970
_("Open an IPython console"),
971
None, 'ipython_console.png',
972
triggered=self.start_ipykernel)
973
if programs.is_module_installed('IPython.frontend.qt', '>=0.13'):
974
interact_menu_actions.append(self.ipykernel_action)
975
self.main.interact_menu_actions += interact_menu_actions
976
self.main.tools_menu_actions += tools_menu_actions
978
return self.menu_actions+interact_menu_actions+tools_menu_actions
980
def register_plugin(self):
981
"""Register plugin in Spyder's main window"""
983
self.main.setCentralWidget(self)
984
self.main.widgetlist.append(self)
986
self.main.add_dockwidget(self)
987
self.inspector = self.main.inspector
988
if self.inspector is not None:
989
self.inspector.set_external_console(self)
990
self.historylog = self.main.historylog
991
self.connect(self, SIGNAL("edit_goto(QString,int,QString)"),
992
self.main.editor.load)
993
self.connect(self, SIGNAL("edit_goto(QString,int,QString,bool)"),
994
lambda fname, lineno, word, processevents:
995
self.main.editor.load(fname,lineno,word,
996
processevents=processevents))
997
self.connect(self.main.editor,
998
SIGNAL('run_in_current_extconsole(QString,QString,QString,bool)'),
999
self.run_script_in_current_shell)
1000
self.connect(self.main.editor, SIGNAL("open_dir(QString)"),
1001
self.set_current_shell_working_directory)
1002
self.connect(self.main.workingdirectory,
1003
SIGNAL("set_current_console_wd(QString)"),
1004
self.set_current_shell_working_directory)
1005
self.connect(self, SIGNAL('focus_changed()'),
1006
self.main.plugin_focus_changed)
1007
self.connect(self, SIGNAL('redirect_stdio(bool)'),
1008
self.main.redirect_internalshell_stdio)
1009
expl = self.main.explorer
1010
if expl is not None:
1011
self.connect(expl, SIGNAL("open_terminal(QString)"),
1013
self.connect(expl, SIGNAL("open_interpreter(QString)"),
1014
self.open_interpreter)
1015
pexpl = self.main.projectexplorer
1016
if pexpl is not None:
1017
self.connect(pexpl, SIGNAL("open_terminal(QString)"),
1019
self.connect(pexpl, SIGNAL("open_interpreter(QString)"),
1020
self.open_interpreter)
1022
def closing_plugin(self, cancelable=False):
1023
"""Perform actions before parent main window is closed"""
1024
for shellwidget in self.shellwidgets:
1028
def refresh_plugin(self):
1029
"""Refresh tabwidget"""
1031
if self.tabwidget.count():
1032
shellwidget = self.tabwidget.currentWidget()
1033
editor = shellwidget.shell
1035
widgets = [shellwidget.create_time_label(), 5
1036
]+shellwidget.get_toolbar_buttons()+[5]
1040
self.find_widget.set_editor(editor)
1041
self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets})
1043
shellwidget.update_time_label_visibility()
1044
self.emit(SIGNAL('update_plugin_title()'))
1046
def apply_plugin_settings(self, options):
1047
"""Apply configuration file's plugin settings"""
1048
font = self.get_plugin_font()
1049
showtime = self.get_option('show_elapsed_time')
1050
icontext = self.get_option('show_icontext')
1051
calltips = self.get_option('calltips')
1052
inspector = self.get_option('object_inspector')
1053
wrap = self.get_option('wrap')
1054
compauto = self.get_option('codecompletion/auto')
1055
case_comp = self.get_option('codecompletion/case_sensitive')
1056
show_single = self.get_option('codecompletion/show_single')
1057
compenter = self.get_option('codecompletion/enter_key')
1058
mlc = self.get_option('max_line_count')
1059
for shellwidget in self.shellwidgets:
1060
shellwidget.shell.set_font(font)
1061
shellwidget.set_elapsed_time_visible(showtime)
1062
shellwidget.set_icontext_visible(icontext)
1063
shellwidget.shell.set_calltips(calltips)
1064
if isinstance(shellwidget.shell, ExternalPythonShell):
1065
shellwidget.shell.set_inspector_enabled(inspector)
1066
shellwidget.shell.toggle_wrap_mode(wrap)
1067
shellwidget.shell.set_codecompletion_auto(compauto)
1068
shellwidget.shell.set_codecompletion_case(case_comp)
1069
shellwidget.shell.set_codecompletion_single(show_single)
1070
shellwidget.shell.set_codecompletion_enter(compenter)
1071
shellwidget.shell.setMaximumBlockCount(mlc)
1073
#------ Public API ---------------------------------------------------------
1074
def open_interpreter_at_startup(self):
1075
"""Open an interpreter or an IPython kernel at startup"""
1076
if self.get_option('open_python_at_startup', True):
1077
self.open_interpreter()
1078
if CONF.get('ipython_console', 'open_ipython_at_startup', False):
1079
self.start_ipykernel()
1081
def open_interpreter(self, wdir=None):
1082
"""Open interpreter"""
1085
if not self.main.light:
1086
self.visibility_changed(True)
1087
self.start(fname=None, wdir=unicode(wdir), args='',
1088
interact=True, debug=False, python=True)
1090
def start_ipykernel(self, wdir=None, create_client=True):
1091
"""Start new IPython kernel"""
1092
if create_client and not self.get_option('monitor/enabled'):
1093
QMessageBox.warning(self, _('Open an IPython console'),
1094
_("The console monitor was disabled: the IPython kernel will "
1095
"be started as expected, but an IPython console will have "
1096
"to be connected manually to the kernel."), QMessageBox.Ok)
1098
# Add a WaitCursor visual indication, because it takes too much time
1099
# to display a new console (3 to 5 secs). It's stopped in
1101
# QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
1102
# QApplication.processEvents()
1106
self.main.ipyconsole.visibility_changed(True)
1107
self.start(fname=None, wdir=unicode(wdir), args='',
1108
interact=True, debug=False, python=True,
1109
ipykernel=True, ipyclient=create_client)
1111
def open_terminal(self, wdir=None):
1115
self.start(fname=None, wdir=unicode(wdir), args='',
1116
interact=True, debug=False, python=False)
1118
def run_script(self):
1119
"""Run a Python script"""
1120
self.emit(SIGNAL('redirect_stdio(bool)'), False)
1121
filename, _selfilter = getopenfilename(self, _("Run Python script"),
1122
os.getcwdu(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)")
1123
self.emit(SIGNAL('redirect_stdio(bool)'), True)
1125
self.start(fname=filename, wdir=None, args='',
1126
interact=False, debug=False)
1128
def set_umd_namelist(self):
1129
"""Set UMD excluded modules name list"""
1130
arguments, valid = QInputDialog.getText(self, _('UMD'),
1131
_('UMD excluded modules:\n'
1132
'(example: guidata, guiqwt)'),
1134
", ".join(self.get_option('umd/namelist')))
1136
arguments = unicode(arguments)
1138
namelist = arguments.replace(' ', '').split(',')
1139
fixed_namelist = [module_name for module_name in namelist
1140
if programs.is_module_installed(module_name)]
1141
invalid = ", ".join(set(namelist)-set(fixed_namelist))
1143
QMessageBox.warning(self, _('UMD'),
1144
_("The following modules are not "
1145
"installed on your machine:\n%s"
1146
) % invalid, QMessageBox.Ok)
1147
QMessageBox.information(self, _('UMD'),
1148
_("Please note that these changes will "
1149
"be applied only to new Python/IPython "
1150
"interpreters"), QMessageBox.Ok)
1153
self.set_option('umd/namelist', fixed_namelist)
1155
def go_to_error(self, text):
1156
"""Go to error if relevant"""
1157
match = get_error_match(unicode(text))
1159
fname, lnb = match.groups()
1160
self.emit(SIGNAL("edit_goto(QString,int,QString)"),
1161
osp.abspath(fname), int(lnb), '')
1164
def dragEnterEvent(self, event):
1165
"""Reimplement Qt method
1166
Inform Qt about the types of data that the widget accepts"""
1167
source = event.mimeData()
1168
if source.hasUrls():
1169
if mimedata2url(source):
1170
pathlist = mimedata2url(source)
1171
shellwidget = self.tabwidget.currentWidget()
1172
if all([is_python_script(unicode(qstr)) for qstr in pathlist]):
1173
event.acceptProposedAction()
1174
elif shellwidget is None or not shellwidget.is_running():
1177
event.acceptProposedAction()
1180
elif source.hasText():
1181
event.acceptProposedAction()
1183
def dropEvent(self, event):
1184
"""Reimplement Qt method
1185
Unpack dropped data and handle it"""
1186
source = event.mimeData()
1187
shellwidget = self.tabwidget.currentWidget()
1188
if source.hasText():
1189
qstr = source.text()
1190
if is_python_script(unicode(qstr)):
1193
shellwidget.shell.insert_text(qstr)
1194
elif source.hasUrls():
1195
pathlist = mimedata2url(source)
1196
if all([is_python_script(unicode(qstr)) for qstr in pathlist]):
1197
for fname in pathlist:
1200
shellwidget.shell.drop_pathlist(pathlist)
1201
event.acceptProposedAction()