~ubuntu-branches/ubuntu/trusty/spyder/trusty-backports

« back to all changes in this revision

Viewing changes to spyderlib/widgets/internalshell.py

  • Committer: Bazaar Package Importer
  • Author(s): Picca Frédéric-Emmanuel
  • Date: 2011-03-05 18:03:43 UTC
  • mfrom: (1.1.3 upstream)
  • Revision ID: james.westby@ubuntu.com-20110305180343-its88tucbyvtevjf
Tags: 2.0.8-1
* Imported Upstream version 2.0.8 (Closes: #609789)
* add a watch file
* build for all python2 versions (it can be use as module by other packages)
* change the documentation section to Programming/Python
* use the Recommendes found in the documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Copyright © 2009-2010 Pierre Raybaut
 
4
# Licensed under the terms of the MIT License
 
5
# (see spyderlib/__init__.py for details)
 
6
 
 
7
"""Internal shell widget : PythonShellWidget + Interpreter"""
 
8
 
 
9
# pylint: disable-msg=C0103
 
10
# pylint: disable-msg=R0903
 
11
# pylint: disable-msg=R0911
 
12
# pylint: disable-msg=R0201
 
13
 
 
14
 
 
15
#----Builtins
 
16
import __builtin__
 
17
try:
 
18
    from IPython.deep_reload import reload
 
19
    __builtin__.dreload = reload
 
20
except ImportError:
 
21
    pass
 
22
from spyderlib.widgets.objecteditor import oedit
 
23
__builtin__.oedit = oedit
 
24
 
 
25
 
 
26
import sys, os, threading
 
27
from time import time
 
28
from subprocess import Popen
 
29
 
 
30
STDOUT = sys.stdout
 
31
 
 
32
from PyQt4.QtGui import QMessageBox
 
33
from PyQt4.QtCore import SIGNAL, QString, QObject
 
34
 
 
35
# Local import
 
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
 
44
 
 
45
 
 
46
def create_banner(moreinfo, message=''):
 
47
    """Create shell banner"""
 
48
    if message:
 
49
        message = '\n' + message + '\n'
 
50
    return 'Python %s on %s\n' % (sys.version, sys.platform) + \
 
51
            moreinfo+'\n' + message + '\n'
 
52
 
 
53
 
 
54
class SysOutput(QObject):
 
55
    """Handle standard I/O queue"""
 
56
    def __init__(self):
 
57
        QObject.__init__(self)
 
58
        self.queue = []
 
59
        self.lock = threading.Lock()
 
60
        
 
61
    def write(self, val):
 
62
        self.lock.acquire()
 
63
        self.queue.append(val)
 
64
        self.lock.release()
 
65
        self.emit(SIGNAL("void data_avail()"))
 
66
 
 
67
    def empty_queue(self):
 
68
        self.lock.acquire()
 
69
        s = "".join(self.queue)
 
70
        self.queue = []
 
71
        self.lock.release()
 
72
        return s
 
73
 
 
74
class WidgetProxy(QObject):
 
75
    """Handle Shell widget refresh signal"""
 
76
    def __init__(self):
 
77
        QObject.__init__(self)
 
78
        
 
79
    def new_prompt(self, prompt):
 
80
        self.emit(SIGNAL("new_prompt(QString)"), prompt)
 
81
        
 
82
    def set_readonly(self, state):
 
83
        self.emit(SIGNAL("set_readonly(bool)"), state)
 
84
        
 
85
    def edit(self, filename, external_editor=False):
 
86
        self.emit(SIGNAL("edit(QString,bool)"), filename, external_editor)
 
87
 
 
88
 
 
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'),
 
96
                                   debug, profile)
 
97
        
 
98
        self.set_light_background(light_background)
 
99
        
 
100
        self.multithreaded = multithreaded
 
101
        
 
102
        self.setMaximumBlockCount(max_line_count)
 
103
        
 
104
        if font is not None:
 
105
            self.set_font(font)
 
106
        
 
107
        # KeyboardInterrupt support
 
108
        self.interrupted = False # used only for not-multithreaded mode
 
109
        self.connect(self, SIGNAL("keyboard_interrupt()"),
 
110
                     self.keyboard_interrupt)
 
111
        
 
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)
 
118
        
 
119
        # keyboard events management
 
120
        self.eventqueue = []
 
121
 
 
122
        # Init interpreter
 
123
        self.exitfunc = exitfunc
 
124
        self.commands = commands
 
125
        self.message = message
 
126
        self.interpreter = None
 
127
        self.start_interpreter(namespace)
 
128
        
 
129
        # Clear status bar
 
130
        self.emit(SIGNAL("status(QString)"), QString())
 
131
                
 
132
                
 
133
    #------ Interpreter
 
134
    def start_interpreter(self, namespace):
 
135
        """Start Python interpreter"""
 
136
        self.clear()
 
137
        
 
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()
 
154
        
 
155
        # interpreter banner
 
156
        banner = create_banner(self.tr('Type "copyright", "credits" or "license" for more information.'), self.message)
 
157
        self.write(banner, prompt=True)
 
158
 
 
159
        # Initial commands
 
160
        for cmd in self.commands:
 
161
            self.run_command(cmd, history=False, new_prompt=False)
 
162
                
 
163
        # First prompt
 
164
        self.new_prompt(self.interpreter.p1)
 
165
        self.emit(SIGNAL("refresh()"))
 
166
 
 
167
        return self.interpreter
 
168
 
 
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')
 
174
        
 
175
    def edit_script(self, filename, external_editor):
 
176
        filename = unicode(filename)
 
177
        if external_editor:
 
178
            self.external_editor(filename)
 
179
        else:
 
180
            self.parent().edit_script(filename)            
 
181
                                    
 
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()
 
185
        if data:
 
186
            self.write(data)
 
187
            self.repaint()
 
188
        
 
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()
 
192
        if data:
 
193
            self.write(data, error=True)
 
194
            self.flush(error=True)
 
195
            self.repaint()
 
196
 
 
197
 
 
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'),
 
205
                           triggered=self.help)
 
206
        self.menu.addAction(self.help_action)
 
207
 
 
208
    def help(self):
 
209
        """Help on Spyder console"""
 
210
        QMessageBox.about(self,
 
211
            translate("InternalShell", "Help"),
 
212
            self.tr("""<b>%1</b>
 
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
 
217
            <p><i>%6</i><br>    !ls
 
218
            <p><i>%7</i><br>    object?
 
219
            <p><i>%8</i><br>    result = oedit(object)
 
220
            """) \
 
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:')) )
 
229
                
 
230
                
 
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')
 
237
        try:
 
238
            if goto > 0 and goto_option:
 
239
                Popen(r'%s "%s" %s%d' % (editor_path, filename,
 
240
                                         goto_option, goto))
 
241
            else:
 
242
                Popen(r'%s "%s"' % (editor_path, filename))
 
243
        except OSError:
 
244
            self.write_error("External editor was not found:"
 
245
                             " %s\n" % editor_path)
 
246
 
 
247
 
 
248
    #------ I/O
 
249
    def flush(self, error=False, prompt=False):
 
250
        """Reimplement PythonShellWidget method"""
 
251
        PythonShellWidget.flush(self, error=error, prompt=prompt)
 
252
        if self.interrupted:
 
253
            self.interrupted = False
 
254
            raise KeyboardInterrupt
 
255
 
 
256
 
 
257
    #------ Clear terminal
 
258
    def clear_terminal(self):
 
259
        """Reimplement ShellBaseWidget method"""
 
260
        self.clear()
 
261
        self.new_prompt(self.interpreter.p2 if self.interpreter.more else self.interpreter.p1)
 
262
 
 
263
 
 
264
    #------ Keyboard events
 
265
    def on_enter(self, command):
 
266
        """on_enter"""
 
267
        if self.profile:
 
268
            # Simple profiling test
 
269
            t0 = time()
 
270
            for _ in range(10):
 
271
                self.execute_command(command)
 
272
            self.insert_text(u"\n<Δt>=%dms\n" % (1e2*(time()-t0)))
 
273
            self.new_prompt(self.interpreter.p1)
 
274
        else:
 
275
            self.execute_command(command)
 
276
        self.__flush_eventqueue()
 
277
 
 
278
    def keyPressEvent(self, event):
 
279
        """
 
280
        Reimplement Qt Method
 
281
        Enhanced keypress event handler
 
282
        """
 
283
        if self.preprocess_keyevent(event):
 
284
            # Event was accepted in self.preprocess_keyevent
 
285
            return
 
286
        self.postprocess_keyevent(event)
 
287
        
 
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)
 
293
        
 
294
    #------ Command execution
 
295
    def keyboard_interrupt(self):
 
296
        """Simulate keyboard interrupt"""
 
297
        if self.multithreaded:
 
298
            self.interpreter.raise_keyboard_interrupt()
 
299
        else:
 
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()
 
305
            else:
 
306
                self.interrupted = True
 
307
 
 
308
    def execute_lines(self, lines):
 
309
        """
 
310
        Execute a set of lines as multiple command
 
311
        lines: multiple lines of text to be executed as single commands
 
312
        """
 
313
        for line in lines.splitlines():
 
314
            stripped_line = line.strip()
 
315
            if stripped_line.startswith('#'):
 
316
                continue
 
317
            self.write(line+os.linesep, flush=True)
 
318
            self.execute_command(line+"\n")
 
319
            self.flush()
 
320
        
 
321
    def execute_command(self, cmd):
 
322
        """
 
323
        Execute a command
 
324
        cmd: one-line command only, with '\n' at the end
 
325
        """
 
326
        if cmd.endswith('\n'):
 
327
            cmd = cmd[:-1]
 
328
        # cls command
 
329
        if cmd == 'cls':
 
330
            self.clear_terminal()
 
331
            return
 
332
        self.run_command(cmd)
 
333
       
 
334
    def run_command(self, cmd, history=True, new_prompt=True):
 
335
        """Run command in interpreter"""
 
336
        if not cmd:
 
337
            cmd = ''
 
338
        else:
 
339
            if history:
 
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()"))
 
345
    
 
346
    
 
347
    #------ Code completion / Calltips
 
348
    def _eval(self, text):
 
349
        """Is text a valid object?"""
 
350
        return self.interpreter.eval(text)
 
351
                
 
352
    def get_dir(self, objtxt):
 
353
        """Return dir(object)"""
 
354
        obj, valid = self._eval(objtxt)
 
355
        if valid:
 
356
            return getobjdir(obj)
 
357
        
 
358
    def get_globals_keys(self):
 
359
        """Return shell globals() keys"""
 
360
        return self.interpreter.namespace.keys()
 
361
        
 
362
    def get_cdlistdir(self):
 
363
        """Return shell current directory list dir"""
 
364
        return os.listdir(os.getcwdu())
 
365
                
 
366
    def iscallable(self, objtxt):
 
367
        """Is object callable?"""
 
368
        obj, valid = self._eval(objtxt)
 
369
        if valid:
 
370
            return callable(obj)
 
371
    
 
372
    def get_arglist(self, objtxt):
 
373
        """Get func/method argument list"""
 
374
        obj, valid = self._eval(objtxt)
 
375
        if valid:
 
376
            return getargtxt(obj)
 
377
    
 
378
    def get__doc__(self, objtxt):
 
379
        """Get object __doc__"""
 
380
        obj, valid = self._eval(objtxt)
 
381
        if valid:
 
382
            return obj.__doc__
 
383
    
 
384
    def get_doc(self, objtxt):
 
385
        """Get object documentation"""
 
386
        obj, valid = self._eval(objtxt)
 
387
        if valid:
 
388
            return getdoc(obj)
 
389
    
 
390
    def get_source(self, objtxt):
 
391
        """Get object source"""
 
392
        obj, valid = self._eval(objtxt)
 
393
        if valid:
 
394
            return getsource(obj)
 
395
 
 
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)