~vorlon/ubuntu/saucy/gourmet/trunk

« back to all changes in this revision

Viewing changes to src/lib/plugins/python_shell/ipython_view.py

  • Committer: Bazaar Package Importer
  • Author(s): Rolf Leggewie
  • Date: 2009-02-10 17:34:51 UTC
  • mfrom: (2.1.4 squeeze)
  • Revision ID: james.westby@ubuntu.com-20090210173451-bt0a6j0ut71oxqes
Tags: 0.14.5-1
* new upstream release
* 0.14.5 no longer conflicts with newer versions of python-pysqlite and
  python-sqlalchemy.  Relax dependencies accordingly.
* all patches have been pushed upstream, so they can be dropped in Debian.
  The manpage was forgotten when preparing the tarball upstream, so it
  will stick around for another release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
'''
 
3
Provides IPython console widget.
 
4
 
 
5
@author: Eitan Isaacson
 
6
@organization: IBM Corporation
 
7
@copyright: Copyright (c) 2007 IBM Corporation
 
8
@license: BSD
 
9
 
 
10
All rights reserved. This program and the accompanying materials are made 
 
11
available under the terms of the BSD which accompanies this distribution, and 
 
12
is available at U{http://www.opensource.org/licenses/bsd-license.php}
 
13
'''
 
14
 
 
15
import gtk, gobject
 
16
import re
 
17
import sys
 
18
import os
 
19
import pango
 
20
from StringIO import StringIO
 
21
import thread
 
22
 
 
23
try:
 
24
  import IPython
 
25
except ImportError:
 
26
  IPython = None
 
27
 
 
28
class IterableIPShell:
 
29
  '''
 
30
  Create an IPython instance. Does not start a blocking event loop,
 
31
  instead allow single iterations. This allows embedding in GTK+ 
 
32
  without blockage.
 
33
 
 
34
  @ivar IP: IPython instance.
 
35
  @type IP: IPython.iplib.InteractiveShell
 
36
  @ivar iter_more: Indicates if the line executed was a complete command,
 
37
  or we should wait for more.
 
38
  @type iter_more: integer
 
39
  @ivar history_level: The place in history where we currently are 
 
40
  when pressing up/down.
 
41
  @type history_level: integer
 
42
  @ivar complete_sep: Seperation delimeters for completion function.
 
43
  @type complete_sep: _sre.SRE_Pattern
 
44
  '''
 
45
  def __init__(self,argv=[],user_ns=None,user_global_ns=None, 
 
46
               cin=None, cout=None,cerr=None, input_func=None):
 
47
    '''
 
48
    
 
49
    
 
50
    @param argv: Command line options for IPython
 
51
    @type argv: list
 
52
    @param user_ns: User namespace.
 
53
    @type user_ns: dictionary
 
54
    @param user_global_ns: User global namespace.
 
55
    @type user_global_ns: dictionary.
 
56
    @param cin: Console standard input.
 
57
    @type cin: IO stream
 
58
    @param cout: Console standard output.
 
59
    @type cout: IO stream 
 
60
    @param cerr: Console standard error.
 
61
    @type cerr: IO stream
 
62
    @param input_func: Replacement for builtin raw_input()
 
63
    @type input_func: function
 
64
    '''
 
65
    if input_func:
 
66
      IPython.iplib.raw_input_original = input_func
 
67
    if cin:
 
68
      IPython.Shell.Term.cin = cin
 
69
    if cout:
 
70
      IPython.Shell.Term.cout = cout
 
71
    if cerr:
 
72
      IPython.Shell.Term.cerr = cerr
 
73
 
 
74
    # This is to get rid of the blockage that accurs during 
 
75
    # IPython.Shell.InteractiveShell.user_setup()
 
76
    IPython.iplib.raw_input = lambda x: None
 
77
 
 
78
    self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr)
 
79
    os.environ['TERM'] = 'dumb'
 
80
    excepthook = sys.excepthook 
 
81
    self.IP = IPython.Shell.make_IPython(
 
82
      argv,user_ns=user_ns,
 
83
      user_global_ns=user_global_ns,
 
84
      embedded=True,
 
85
      shell_class=IPython.Shell.InteractiveShell)
 
86
    self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd),
 
87
                                            header='IPython system call: ',
 
88
                                            verbose=self.IP.rc.system_verbose)
 
89
    sys.excepthook = excepthook
 
90
    self.iter_more = 0
 
91
    self.history_level = 0
 
92
    self.complete_sep =  re.compile('[\s\{\}\[\]\(\)]')
 
93
 
 
94
  def execute(self):
 
95
    '''
 
96
    Executes the current line provided by the shell object.
 
97
    '''
 
98
    self.history_level = 0
 
99
    orig_stdout = sys.stdout
 
100
    sys.stdout = IPython.Shell.Term.cout
 
101
    try:
 
102
      line = self.IP.raw_input(None, self.iter_more)
 
103
      if self.IP.autoindent:
 
104
        self.IP.readline_startup_hook(None)
 
105
    except KeyboardInterrupt:
 
106
      self.IP.write('\nKeyboardInterrupt\n')
 
107
      self.IP.resetbuffer()
 
108
      # keep cache in sync with the prompt counter:
 
109
      self.IP.outputcache.prompt_count -= 1
 
110
        
 
111
      if self.IP.autoindent:
 
112
        self.IP.indent_current_nsp = 0
 
113
      self.iter_more = 0
 
114
    except:
 
115
      self.IP.showtraceback()
 
116
    else:
 
117
      self.iter_more = self.IP.push(line)
 
118
      if (self.IP.SyntaxTB.last_syntax_error and
 
119
          self.IP.rc.autoedit_syntax):
 
120
        self.IP.edit_syntax_error()
 
121
    if self.iter_more:
 
122
      self.prompt = str(self.IP.outputcache.prompt2).strip()
 
123
      if self.IP.autoindent:
 
124
        self.IP.readline_startup_hook(self.IP.pre_readline)
 
125
    else:
 
126
      self.prompt = str(self.IP.outputcache.prompt1).strip()
 
127
    sys.stdout = orig_stdout
 
128
 
 
129
  def historyBack(self):
 
130
    '''
 
131
    Provides one history command back.
 
132
    
 
133
    @return: The command string.
 
134
    @rtype: string
 
135
    '''
 
136
    self.history_level -= 1
 
137
    return self._getHistory()
 
138
  
 
139
  def historyForward(self):
 
140
    '''
 
141
    Provides one history command forward.
 
142
    
 
143
    @return: The command string.
 
144
    @rtype: string
 
145
    '''
 
146
    self.history_level += 1
 
147
    return self._getHistory()
 
148
  
 
149
  def _getHistory(self):
 
150
    """
 
151
    Get's the command string of the current history level.
 
152
    
 
153
    @return: Historic command string.
 
154
    @rtype: string
 
155
    """
 
156
    try:
 
157
      rv = self.IP.user_ns['In'][self.history_level].strip('\n')
 
158
    except IndexError:
 
159
      self.history_level = 0
 
160
      rv = ''
 
161
    return rv
 
162
 
 
163
  def updateNamespace(self, ns_dict):
 
164
    '''
 
165
    Add the current dictionary to the shell namespace.
 
166
    
 
167
    @param ns_dict: A dictionary of symbol-values.
 
168
    @type ns_dict: dictionary
 
169
    '''
 
170
    self.IP.user_ns.update(ns_dict)
 
171
 
 
172
  def complete(self, line):
 
173
    '''
 
174
    Returns an auto completed line and/or posibilities for completion.
 
175
    
 
176
    @param line: Given line so far.
 
177
    @type line: string
 
178
    
 
179
    @return: Line completed as for as possible, 
 
180
    and possible further completions.
 
181
    @rtype: tuple
 
182
    '''
 
183
    split_line = self.complete_sep.split(line)
 
184
    possibilities = self.IP.complete(split_line[-1])
 
185
    if possibilities:
 
186
      def _commonPrefix(str1, str2):
 
187
        '''
 
188
        Reduction function. returns common prefix of two given strings.
 
189
        
 
190
        @param str1: First string.
 
191
        @type str1: string
 
192
        @param str2: Second string
 
193
        @type str2: string
 
194
        
 
195
        @return: Common prefix to both strings.
 
196
        @rtype: string
 
197
        '''
 
198
        for i in range(len(str1)):
 
199
          if not str2.startswith(str1[:i+1]):
 
200
            return str1[:i]
 
201
        return str1
 
202
      common_prefix = reduce(_commonPrefix, possibilities)
 
203
      completed = line[:-len(split_line[-1])]+common_prefix
 
204
    else:
 
205
      completed = line
 
206
    return completed, possibilities
 
207
  
 
208
 
 
209
  def shell(self, cmd,verbose=0,debug=0,header=''):
 
210
    '''
 
211
    Replacement method to allow shell commands without them blocking.
 
212
    
 
213
    @param cmd: Shell command to execute.
 
214
    @type cmd: string
 
215
    @param verbose: Verbosity
 
216
    @type verbose: integer
 
217
    @param debug: Debug level
 
218
    @type debug: integer
 
219
    @param header: Header to be printed before output
 
220
    @type header: string
 
221
    '''
 
222
    stat = 0
 
223
    if verbose or debug: print header+cmd
 
224
    # flush stdout so we don't mangle python's buffering
 
225
    if not debug:
 
226
      input, output = os.popen4(cmd)
 
227
      print output.read()
 
228
      output.close()
 
229
      input.close()
 
230
 
 
231
class ConsoleView(gtk.TextView):
 
232
  """
 
233
  Specialized text view for console-like workflow.
 
234
 
 
235
  @cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
 
236
  @type ANSI_COLORS: dictionary
 
237
 
 
238
  @ivar text_buffer: Widget's text buffer.
 
239
  @type text_buffer: gtk.TextBuffer
 
240
  @ivar color_pat: Regex of terminal color pattern
 
241
  @type color_pat: _sre.SRE_Pattern
 
242
  @ivar mark: Scroll mark for automatic scrolling on input.
 
243
  @type mark: gtk.TextMark
 
244
  @ivar line_start: Start of command line mark.
 
245
  @type line_start: gtk.TextMark
 
246
  """
 
247
  ANSI_COLORS =  {'0;30': 'Black',     '0;31': 'Red',
 
248
                  '0;32': 'Green',     '0;33': 'Brown',
 
249
                  '0;34': 'Blue',      '0;35': 'Purple',
 
250
                  '0;36': 'Cyan',      '0;37': 'LightGray',
 
251
                  '1;30': 'DarkGray',  '1;31': 'DarkRed',
 
252
                  '1;32': 'SeaGreen',  '1;33': 'Yellow',
 
253
                  '1;34': 'LightBlue', '1;35': 'MediumPurple',
 
254
                  '1;36': 'LightCyan', '1;37': 'White'}
 
255
 
 
256
  def __init__(self):
 
257
    '''
 
258
    Initialize console view.
 
259
    '''
 
260
    gtk.TextView.__init__(self)
 
261
    self.modify_font(pango.FontDescription('Mono'))
 
262
    self.set_cursor_visible(True)
 
263
    self.text_buffer = self.get_buffer()
 
264
    self.mark = self.text_buffer.create_mark('scroll_mark', 
 
265
                                             self.text_buffer.get_end_iter(),
 
266
                                             False)
 
267
    for code in self.ANSI_COLORS:
 
268
      self.text_buffer.create_tag(code, 
 
269
                                  foreground=self.ANSI_COLORS[code], 
 
270
                                  weight=700)
 
271
    self.text_buffer.create_tag('0')
 
272
    self.text_buffer.create_tag('notouch', editable=False)
 
273
    self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
 
274
    self.line_start = \
 
275
        self.text_buffer.create_mark('line_start', 
 
276
                                     self.text_buffer.get_end_iter(), True)
 
277
    self.connect('key-press-event', self.onKeyPress)
 
278
    
 
279
  def write(self, text, editable=False):
 
280
    gobject.idle_add(self._write, text, editable)
 
281
 
 
282
  def _write(self, text, editable=False):
 
283
    '''
 
284
    Write given text to buffer.
 
285
    
 
286
    @param text: Text to append.
 
287
    @type text: string
 
288
    @param editable: If true, added text is editable.
 
289
    @type editable: boolean
 
290
    '''
 
291
    segments = self.color_pat.split(text)
 
292
    segment = segments.pop(0)
 
293
    start_mark = self.text_buffer.create_mark(None, 
 
294
                                              self.text_buffer.get_end_iter(), 
 
295
                                              True)
 
296
    self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)
 
297
 
 
298
    if segments:
 
299
      ansi_tags = self.color_pat.findall(text)
 
300
      for tag in ansi_tags:
 
301
        i = segments.index(tag)
 
302
        self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(),
 
303
                                             segments[i+1], tag)
 
304
        segments.pop(i)
 
305
    if not editable:
 
306
      self.text_buffer.apply_tag_by_name('notouch',
 
307
                                         self.text_buffer.get_iter_at_mark(start_mark),
 
308
                                         self.text_buffer.get_end_iter())
 
309
    self.text_buffer.delete_mark(start_mark)
 
310
    self.scroll_mark_onscreen(self.mark)
 
311
 
 
312
 
 
313
  def showPrompt(self, prompt):
 
314
    gobject.idle_add(self._showPrompt, prompt)
 
315
 
 
316
  def _showPrompt(self, prompt):
 
317
    '''
 
318
    Prints prompt at start of line.
 
319
    
 
320
    @param prompt: Prompt to print.
 
321
    @type prompt: string
 
322
    '''
 
323
    self._write(prompt)
 
324
    self.text_buffer.move_mark(self.line_start,
 
325
                               self.text_buffer.get_end_iter())
 
326
 
 
327
  def changeLine(self, text):
 
328
    gobject.idle_add(self._changeLine, text)
 
329
 
 
330
  def _changeLine(self, text):
 
331
    '''
 
332
    Replace currently entered command line with given text.
 
333
    
 
334
    @param text: Text to use as replacement.
 
335
    @type text: string
 
336
    '''
 
337
    iter = self.text_buffer.get_iter_at_mark(self.line_start)
 
338
    iter.forward_to_line_end()
 
339
    self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter)
 
340
    self._write(text, True)
 
341
 
 
342
  def getCurrentLine(self):
 
343
    '''
 
344
    Get text in current command line.
 
345
    
 
346
    @return: Text of current command line.
 
347
    @rtype: string
 
348
    '''
 
349
    rv = self.text_buffer.get_slice(
 
350
      self.text_buffer.get_iter_at_mark(self.line_start),
 
351
      self.text_buffer.get_end_iter(), False)
 
352
    return rv
 
353
 
 
354
  def showReturned(self, text):
 
355
    gobject.idle_add(self._showReturned, text)
 
356
 
 
357
  def _showReturned(self, text):
 
358
    '''
 
359
    Show returned text from last command and print new prompt.
 
360
    
 
361
    @param text: Text to show.
 
362
    @type text: string
 
363
    '''
 
364
    iter = self.text_buffer.get_iter_at_mark(self.line_start)
 
365
    iter.forward_to_line_end()
 
366
    self.text_buffer.apply_tag_by_name(
 
367
      'notouch', 
 
368
      self.text_buffer.get_iter_at_mark(self.line_start),
 
369
      iter)
 
370
    try:
 
371
      self._write('\n'+text)
 
372
    except:
 
373
      pass
 
374
    if text:
 
375
      self._write('\n')
 
376
    self._showPrompt(self.prompt)
 
377
    self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter())
 
378
    self.text_buffer.place_cursor(self.text_buffer.get_end_iter())
 
379
 
 
380
  def onKeyPress(self, widget, event):
 
381
    '''
 
382
    Key press callback used for correcting behavior for console-like 
 
383
    interfaces. For example 'home' should go to prompt, not to begining of
 
384
    line.
 
385
    
 
386
    @param widget: Widget that key press accored in.
 
387
    @type widget: gtk.Widget
 
388
    @param event: Event object
 
389
    @type event: gtk.gdk.Event
 
390
    
 
391
    @return: Return True if event should not trickle.
 
392
    @rtype: boolean
 
393
    '''
 
394
    insert_mark = self.text_buffer.get_insert()
 
395
    insert_iter = self.text_buffer.get_iter_at_mark(insert_mark)
 
396
    selection_mark = self.text_buffer.get_selection_bound()
 
397
    selection_iter = self.text_buffer.get_iter_at_mark(selection_mark)
 
398
    start_iter = self.text_buffer.get_iter_at_mark(self.line_start)
 
399
    if event.keyval == gtk.keysyms.Home:
 
400
      if event.state & gtk.gdk.CONTROL_MASK or event.state & gtk.gdk.MOD1_MASK:
 
401
        pass
 
402
      elif event.state & gtk.gdk.SHIFT_MASK:
 
403
        self.text_buffer.move_mark(insert_mark, start_iter)
 
404
        return True
 
405
      else:
 
406
        self.text_buffer.place_cursor(start_iter)
 
407
        return True
 
408
    elif event.keyval == gtk.keysyms.Left:
 
409
      insert_iter.backward_cursor_position()
 
410
      if not insert_iter.editable(True):
 
411
        return True
 
412
    elif not event.string:
 
413
      pass
 
414
    elif start_iter.compare(insert_iter) <= 0 and \
 
415
          start_iter.compare(selection_iter) <= 0:
 
416
      pass
 
417
    elif start_iter.compare(insert_iter) > 0 and \
 
418
          start_iter.compare(selection_iter) > 0:
 
419
      self.text_buffer.place_cursor(start_iter)
 
420
    elif insert_iter.compare(selection_iter) < 0:
 
421
      self.text_buffer.move_mark(insert_mark, start_iter)
 
422
    elif insert_iter.compare(selection_iter) > 0:
 
423
      self.text_buffer.move_mark(selection_mark, start_iter)             
 
424
 
 
425
    return self.onKeyPressExtend(event)
 
426
 
 
427
  def onKeyPressExtend(self, event):
 
428
    """
 
429
    For some reason we can't extend onKeyPress directly (bug #500900).
 
430
    """
 
431
    pass
 
432
 
 
433
class IPythonView(ConsoleView, IterableIPShell):
 
434
  '''
 
435
  Sub-class of both modified IPython shell and L{ConsoleView} this makes
 
436
  a GTK+ IPython console.
 
437
  '''
 
438
  def __init__(self):
 
439
    '''
 
440
    Initialize. Redirect I/O to console.
 
441
    '''
 
442
    ConsoleView.__init__(self)
 
443
    self.cout = StringIO()
 
444
    IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout, 
 
445
                             input_func=self.raw_input)
 
446
#    self.connect('key_press_event', self.keyPress)
 
447
    self.execute()
 
448
    self.cout.truncate(0)
 
449
    self.showPrompt(self.prompt)
 
450
    self.interrupt = False
 
451
 
 
452
  def raw_input(self, prompt=''):
 
453
    """
 
454
    Custom raw_input() replacement. Get's current line from console buffer.
 
455
    
 
456
    @param prompt: Prompt to print. Here for compatability as replacement.
 
457
    @type prompt: string
 
458
    
 
459
    @return: The current command line text.
 
460
    @rtype: string
 
461
    """
 
462
    if self.interrupt:
 
463
      self.interrupt = False
 
464
      raise KeyboardInterrupt
 
465
    return self.getCurrentLine()
 
466
 
 
467
  def onKeyPressExtend(self, event):
 
468
    '''
 
469
    Key press callback with plenty of shell goodness, like history, 
 
470
    autocompletions, etc.
 
471
    
 
472
    @param widget: Widget that key press occured in.
 
473
    @type widget: gtk.Widget
 
474
    @param event: Event object.
 
475
    @type event: gtk.gdk.Event
 
476
    
 
477
    @return: True if event should not trickle.
 
478
    @rtype: boolean
 
479
    '''
 
480
    if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99:
 
481
      self.interrupt = True
 
482
      self._processLine()
 
483
      return True
 
484
    elif event.keyval == gtk.keysyms.Return:
 
485
      self._processLine()
 
486
      return True
 
487
    elif event.keyval == gtk.keysyms.Up:
 
488
      self.changeLine(self.historyBack())
 
489
      return True
 
490
    elif event.keyval == gtk.keysyms.Down:
 
491
      self.changeLine(self.historyForward())
 
492
      return True
 
493
    elif event.keyval == gtk.keysyms.Tab:
 
494
      if not self.getCurrentLine().strip():
 
495
        return False
 
496
      completed, possibilities = self.complete(self.getCurrentLine())
 
497
      if len(possibilities) > 1:
 
498
        slice = self.getCurrentLine()
 
499
        self.write('\n')
 
500
        for symbol in possibilities:
 
501
          self.write(symbol+'\n')
 
502
        self.showPrompt(self.prompt)
 
503
      self.changeLine(completed or slice)
 
504
      return True
 
505
 
 
506
  def _processLine(self):
 
507
    '''
 
508
    Process current command line.
 
509
    '''
 
510
    self.history_pos = 0
 
511
    self.execute()
 
512
    rv = self.cout.getvalue()
 
513
    if rv: rv = rv.strip('\n')
 
514
    self.showReturned(rv)
 
515
    self.cout.truncate(0)
 
516
    
 
517
if __name__ == '__main__':
 
518
  w = gtk.Window()
 
519
  sw = gtk.ScrolledWindow()
 
520
  w.add(sw)
 
521
  sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
 
522
  w.show()
 
523
  v = IPythonView()
 
524
  sw.add(v)
 
525
  sw.show(); v.show()
 
526
  v.set_wrap_mode(gtk.WRAP_CHAR)
 
527
  gtk.main()