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
"""Internal shell widget : PythonShellWidget + Interpreter"""
9
# pylint: disable-msg=C0103
10
# pylint: disable-msg=R0903
11
# pylint: disable-msg=R0911
12
# pylint: disable-msg=R0201
18
from IPython.deep_reload import reload
19
__builtin__.dreload = reload
22
from spyderlib.widgets.objecteditor import oedit
23
__builtin__.oedit = oedit
26
import sys, os, threading
28
from subprocess import Popen
32
from PyQt4.QtGui import QMessageBox
33
from PyQt4.QtCore import SIGNAL, QString, QObject
36
from spyderlib.utils.qthelpers import translate, create_action, get_std_icon
37
from spyderlib.interpreter import Interpreter
38
from spyderlib.utils.dochelpers import getargtxt, getsource, getdoc, getobjdir
39
#TODO: remove the CONF object and make it work anyway
40
# In fact, this 'CONF' object has nothing to do in package spyderlib.widgets
41
# which should not contain anything directly related to Spyder's main app
42
from spyderlib.config import CONF, get_conf_path
43
from spyderlib.widgets.shell import PythonShellWidget
46
def create_banner(moreinfo, message=''):
47
"""Create shell banner"""
49
message = '\n' + message + '\n'
50
return 'Python %s on %s\n' % (sys.version, sys.platform) + \
51
moreinfo+'\n' + message + '\n'
54
class SysOutput(QObject):
55
"""Handle standard I/O queue"""
57
QObject.__init__(self)
59
self.lock = threading.Lock()
63
self.queue.append(val)
65
self.emit(SIGNAL("void data_avail()"))
67
def empty_queue(self):
69
s = "".join(self.queue)
74
class WidgetProxy(QObject):
75
"""Handle Shell widget refresh signal"""
77
QObject.__init__(self)
79
def new_prompt(self, prompt):
80
self.emit(SIGNAL("new_prompt(QString)"), prompt)
82
def set_readonly(self, state):
83
self.emit(SIGNAL("set_readonly(bool)"), state)
85
def edit(self, filename, external_editor=False):
86
self.emit(SIGNAL("edit(QString,bool)"), filename, external_editor)
89
class InternalShell(PythonShellWidget):
90
"""Shell base widget: link between PythonShellWidget and Interpreter"""
91
def __init__(self, parent=None, namespace=None, commands=[], message="",
92
max_line_count=300, font=None, debug=False, exitfunc=None,
93
profile=False, multithreaded=True, light_background=True):
94
PythonShellWidget.__init__(self, parent,
95
get_conf_path('.history_internal.py'),
98
self.set_light_background(light_background)
100
self.multithreaded = multithreaded
102
self.setMaximumBlockCount(max_line_count)
107
# KeyboardInterrupt support
108
self.interrupted = False # used only for not-multithreaded mode
109
self.connect(self, SIGNAL("keyboard_interrupt()"),
110
self.keyboard_interrupt)
112
# Code completion / calltips
113
getcfg = lambda option: CONF.get('internal_console', option)
114
case_sensitive = getcfg('codecompletion/case_sensitive')
115
show_single = getcfg('codecompletion/show_single')
116
self.set_codecompletion_case(case_sensitive)
117
self.set_codecompletion_single(show_single)
119
# keyboard events management
123
self.exitfunc = exitfunc
124
self.commands = commands
125
self.message = message
126
self.interpreter = None
127
self.start_interpreter(namespace)
130
self.emit(SIGNAL("status(QString)"), QString())
134
def start_interpreter(self, namespace):
135
"""Start Python interpreter"""
138
if self.interpreter is not None:
139
self.interpreter.closing()
140
self.interpreter = Interpreter(namespace, self.exitfunc,
141
SysOutput, WidgetProxy, self.debug)
142
self.connect(self.interpreter.stdout_write,
143
SIGNAL("void data_avail()"), self.stdout_avail)
144
self.connect(self.interpreter.stderr_write,
145
SIGNAL("void data_avail()"), self.stderr_avail)
146
self.connect(self.interpreter.widget_proxy,
147
SIGNAL("set_readonly(bool)"), self.setReadOnly)
148
self.connect(self.interpreter.widget_proxy,
149
SIGNAL("new_prompt(QString)"), self.new_prompt)
150
self.connect(self.interpreter.widget_proxy,
151
SIGNAL("edit(QString,bool)"), self.edit_script)
152
if self.multithreaded:
153
self.interpreter.start()
156
banner = create_banner(self.tr('Type "copyright", "credits" or "license" for more information.'), self.message)
157
self.write(banner, prompt=True)
160
for cmd in self.commands:
161
self.run_command(cmd, history=False, new_prompt=False)
164
self.new_prompt(self.interpreter.p1)
165
self.emit(SIGNAL("refresh()"))
167
return self.interpreter
169
def exit_interpreter(self):
170
"""Exit interpreter"""
171
self.interpreter.exit_flag = True
172
if self.multithreaded:
173
self.interpreter.stdin_write.write('\n')
175
def edit_script(self, filename, external_editor):
176
filename = unicode(filename)
178
self.external_editor(filename)
180
self.parent().edit_script(filename)
182
def stdout_avail(self):
183
"""Data is available in stdout, let's empty the queue and write it!"""
184
data = self.interpreter.stdout_write.empty_queue()
189
def stderr_avail(self):
190
"""Data is available in stderr, let's empty the queue and write it!"""
191
data = self.interpreter.stderr_write.empty_queue()
193
self.write(data, error=True)
194
self.flush(error=True)
198
#----- Menus, actions, ...
199
def setup_context_menu(self):
200
"""Reimplement PythonShellWidget method"""
201
PythonShellWidget.setup_context_menu(self)
202
self.help_action = create_action(self,
203
translate("InternalShell", "Help..."),
204
icon=get_std_icon('DialogHelpButton'),
206
self.menu.addAction(self.help_action)
209
"""Help on Spyder console"""
210
QMessageBox.about(self,
211
translate("InternalShell", "Help"),
213
<p><i>%2</i><br> edit foobar.py
214
<p><i>%3</i><br> xedit foobar.py
215
<p><i>%4</i><br> run foobar.py
216
<p><i>%5</i><br> clear x, y
218
<p><i>%7</i><br> object?
219
<p><i>%8</i><br> result = oedit(object)
221
.arg(translate("InternalShell", 'Shell special commands:')) \
222
.arg(translate("InternalShell", 'Internal editor:')) \
223
.arg(translate("InternalShell", 'External editor:')) \
224
.arg(translate("InternalShell", 'Run script:')) \
225
.arg(translate("InternalShell", 'Remove references:')) \
226
.arg(translate("InternalShell", 'System commands:')) \
227
.arg(translate("InternalShell", 'Python help:')) \
228
.arg(translate("InternalShell", 'GUI-based editor:')) )
231
#------ External editing
232
def external_editor(self, filename, goto=-1):
233
"""Edit in an external editor
234
Recommended: SciTE (e.g. to go to line where an error did occur)"""
235
editor_path = CONF.get('internal_console', 'external_editor/path')
236
goto_option = CONF.get('internal_console', 'external_editor/gotoline')
238
if goto > 0 and goto_option:
239
Popen(r'%s "%s" %s%d' % (editor_path, filename,
242
Popen(r'%s "%s"' % (editor_path, filename))
244
self.write_error("External editor was not found:"
245
" %s\n" % editor_path)
249
def flush(self, error=False, prompt=False):
250
"""Reimplement PythonShellWidget method"""
251
PythonShellWidget.flush(self, error=error, prompt=prompt)
253
self.interrupted = False
254
raise KeyboardInterrupt
257
#------ Clear terminal
258
def clear_terminal(self):
259
"""Reimplement ShellBaseWidget method"""
261
self.new_prompt(self.interpreter.p2 if self.interpreter.more else self.interpreter.p1)
264
#------ Keyboard events
265
def on_enter(self, command):
268
# Simple profiling test
271
self.execute_command(command)
272
self.insert_text(u"\n<Δt>=%dms\n" % (1e2*(time()-t0)))
273
self.new_prompt(self.interpreter.p1)
275
self.execute_command(command)
276
self.__flush_eventqueue()
278
def keyPressEvent(self, event):
280
Reimplement Qt Method
281
Enhanced keypress event handler
283
if self.preprocess_keyevent(event):
284
# Event was accepted in self.preprocess_keyevent
286
self.postprocess_keyevent(event)
288
def __flush_eventqueue(self):
289
"""Flush keyboard event queue"""
290
while self.eventqueue:
291
past_event = self.eventqueue.pop(0)
292
self.postprocess_keyevent(past_event)
294
#------ Command execution
295
def keyboard_interrupt(self):
296
"""Simulate keyboard interrupt"""
297
if self.multithreaded:
298
self.interpreter.raise_keyboard_interrupt()
300
if self.interpreter.more:
301
self.write_error("\nKeyboardInterrupt\n")
302
self.interpreter.more = False
303
self.new_prompt(self.interpreter.p1)
304
self.interpreter.resetbuffer()
306
self.interrupted = True
308
def execute_lines(self, lines):
310
Execute a set of lines as multiple command
311
lines: multiple lines of text to be executed as single commands
313
for line in lines.splitlines():
314
stripped_line = line.strip()
315
if stripped_line.startswith('#'):
317
self.write(line+os.linesep, flush=True)
318
self.execute_command(line+"\n")
321
def execute_command(self, cmd):
324
cmd: one-line command only, with '\n' at the end
326
if cmd.endswith('\n'):
330
self.clear_terminal()
332
self.run_command(cmd)
334
def run_command(self, cmd, history=True, new_prompt=True):
335
"""Run command in interpreter"""
340
self.add_to_history(cmd)
341
self.interpreter.stdin_write.write(cmd + '\n')
342
if not self.multithreaded:
343
self.interpreter.run_line()
344
self.emit(SIGNAL("refresh()"))
347
#------ Code completion / Calltips
348
def _eval(self, text):
349
"""Is text a valid object?"""
350
return self.interpreter.eval(text)
352
def get_dir(self, objtxt):
353
"""Return dir(object)"""
354
obj, valid = self._eval(objtxt)
356
return getobjdir(obj)
358
def get_globals_keys(self):
359
"""Return shell globals() keys"""
360
return self.interpreter.namespace.keys()
362
def get_cdlistdir(self):
363
"""Return shell current directory list dir"""
364
return os.listdir(os.getcwdu())
366
def iscallable(self, objtxt):
367
"""Is object callable?"""
368
obj, valid = self._eval(objtxt)
372
def get_arglist(self, objtxt):
373
"""Get func/method argument list"""
374
obj, valid = self._eval(objtxt)
376
return getargtxt(obj)
378
def get__doc__(self, objtxt):
379
"""Get object __doc__"""
380
obj, valid = self._eval(objtxt)
384
def get_doc(self, objtxt):
385
"""Get object documentation"""
386
obj, valid = self._eval(objtxt)
390
def get_source(self, objtxt):
391
"""Get object source"""
392
obj, valid = self._eval(objtxt)
394
return getsource(obj)
396
def is_defined(self, objtxt, force_import=False):
397
"""Return True if object is defined"""
398
return self.interpreter.is_defined(objtxt, force_import)