/[ascend]/trunk/pygtk/ipython_view.py
ViewVC logotype

Contents of /trunk/pygtk/ipython_view.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1924 - (show annotations) (download) (as text)
Mon Oct 6 08:21:11 2008 UTC (13 years, 8 months ago) by jpye
File MIME type: text/x-python
File size: 16256 byte(s)
fixed bug #380.
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 Exception,e:
26 raise Exception("Error importing IPython (%s)" % str(e))
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 self._write('\n'+text)
371 if text:
372 self._write('\n')
373 self._showPrompt(self.prompt)
374 self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter())
375 self.text_buffer.place_cursor(self.text_buffer.get_end_iter())
376
377 def onKeyPress(self, widget, event):
378 '''
379 Key press callback used for correcting behavior for console-like
380 interfaces. For example 'home' should go to prompt, not to begining of
381 line.
382
383 @param widget: Widget that key press accored in.
384 @type widget: gtk.Widget
385 @param event: Event object
386 @type event: gtk.gdk.Event
387
388 @return: Return True if event should not trickle.
389 @rtype: boolean
390 '''
391 insert_mark = self.text_buffer.get_insert()
392 insert_iter = self.text_buffer.get_iter_at_mark(insert_mark)
393 selection_mark = self.text_buffer.get_selection_bound()
394 selection_iter = self.text_buffer.get_iter_at_mark(selection_mark)
395 start_iter = self.text_buffer.get_iter_at_mark(self.line_start)
396 if event.keyval == gtk.keysyms.Home:
397 if event.state & gtk.gdk.CONTROL_MASK or event.state & gtk.gdk.MOD1_MASK:
398 pass
399 elif event.state & gtk.gdk.SHIFT_MASK:
400 self.text_buffer.move_mark(insert_mark, start_iter)
401 return True
402 else:
403 self.text_buffer.place_cursor(start_iter)
404 return True
405 elif event.keyval == gtk.keysyms.Left:
406 insert_iter.backward_cursor_position()
407 if not insert_iter.editable(True):
408 return True
409 elif not event.string:
410 pass
411 elif start_iter.compare(insert_iter) <= 0 and \
412 start_iter.compare(selection_iter) <= 0:
413 pass
414 elif start_iter.compare(insert_iter) > 0 and \
415 start_iter.compare(selection_iter) > 0:
416 self.text_buffer.place_cursor(start_iter)
417 elif insert_iter.compare(selection_iter) < 0:
418 self.text_buffer.move_mark(insert_mark, start_iter)
419 elif insert_iter.compare(selection_iter) > 0:
420 self.text_buffer.move_mark(selection_mark, start_iter)
421
422 return self.onKeyPressExtend(event)
423
424 def onKeyPressExtend(self, event):
425 '''
426 For some reason we can't extend onKeyPress directly (bug #500900).
427 '''
428 pass
429
430 class IPythonView(ConsoleView, IterableIPShell):
431 '''
432 Sub-class of both modified IPython shell and L{ConsoleView} this makes
433 a GTK+ IPython console.
434 '''
435 def __init__(self):
436 '''
437 Initialize. Redirect I/O to console.
438 '''
439 ConsoleView.__init__(self)
440 self.cout = StringIO()
441 IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout,
442 input_func=self.raw_input)
443 # self.connect('key_press_event', self.keyPress)
444 self.execute()
445 self.cout.truncate(0)
446 self.showPrompt(self.prompt)
447 self.interrupt = False
448
449 def raw_input(self, prompt=''):
450 '''
451 Custom raw_input() replacement. Get's current line from console buffer.
452
453 @param prompt: Prompt to print. Here for compatability as replacement.
454 @type prompt: string
455
456 @return: The current command line text.
457 @rtype: string
458 '''
459 if self.interrupt:
460 self.interrupt = False
461 raise KeyboardInterrupt
462 return self.getCurrentLine()
463
464 def onKeyPressExtend(self, event):
465 '''
466 Key press callback with plenty of shell goodness, like history,
467 autocompletions, etc.
468
469 @param widget: Widget that key press occured in.
470 @type widget: gtk.Widget
471 @param event: Event object.
472 @type event: gtk.gdk.Event
473
474 @return: True if event should not trickle.
475 @rtype: boolean
476 '''
477 if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99:
478 self.interrupt = True
479 self._processLine()
480 return True
481 elif event.keyval == gtk.keysyms.Return:
482 self._processLine()
483 return True
484 elif event.keyval == gtk.keysyms.Up:
485 self.changeLine(self.historyBack())
486 return True
487 elif event.keyval == gtk.keysyms.Down:
488 self.changeLine(self.historyForward())
489 return True
490 elif event.keyval == gtk.keysyms.Tab:
491 if not self.getCurrentLine().strip():
492 return False
493 completed, possibilities = self.complete(self.getCurrentLine())
494 if len(possibilities) > 1:
495 slice = self.getCurrentLine()
496 self.write('\n')
497 for symbol in possibilities:
498 self.write(symbol+'\n')
499 self.showPrompt(self.prompt)
500 self.changeLine(completed or slice)
501 return True
502
503 def _processLine(self):
504 '''
505 Process current command line.
506 '''
507 self.history_pos = 0
508 self.execute()
509 rv = self.cout.getvalue()
510 if rv: rv = rv.strip('\n')
511 self.showReturned(rv)
512 self.cout.truncate(0)
513

john.pye@anu.edu.au
ViewVC Help
Powered by ViewVC 1.1.22