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

Contents of /trunk/pygtk/ipython_view.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3305 - (show annotations) (download) (as text)
Tue Jan 2 00:55:49 2018 UTC (11 months, 1 week ago) by jpye
File MIME type: text/x-python
File size: 20390 byte(s)
note about source of patch

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 # this file is a modified version of source code from the Accerciser project
15 # http://live.gnome.org/accerciser
16 #
17 # This version obtained from the NS-3 project at nsnam.org,
18 # http://code.nsnam.org/ns-3-dev/file/35b461a12b26/src/visualizer/visualizer/ipython_view.py
19 # also patched as per
20 # https://bugzilla.gnome.org/show_bug.cgi?id=776301#c4
21
22 import gtk, gobject
23 import re
24 import sys
25 import os
26 import pango
27 from StringIO import StringIO
28 import IPython
29
30 from pkg_resources import parse_version
31
32 try:
33 import IPython
34 except ImportError:
35 ##@ var IPython
36 #
37 IPython = None
38
39 ## IterableIPShell class
40 class IterableIPShell:
41 ## @var IP
42 # IP
43 ## @var iter_more
44 # iterate more
45 ## @var history_level
46 # history level
47 ## @var complete_sep
48 # separators
49 ## @var prompt
50 # prompt
51 ## @var header
52 # header
53 ## @var config
54 # config
55 ## @var user_ns
56 # user_ns
57 ## @var old_stdout
58 # saved stdout
59 ## @var old_stderr
60 # saved stderr
61 ## @var system
62 # system
63 ## @var cfg
64 # configuration
65 ## @var colors
66 # colors
67 ## @var raw_input_original
68 # original raw input
69 ## @var stdin
70 # cin
71 ## @var stdout
72 # cout
73 ## @var stderr
74 # cerr
75 ## @var raw_input
76 # raw input
77 ## @var excepthook
78 # exception hook
79 ## Constructor
80 def __init__(self,argv=None,user_ns=None,user_global_ns=None,
81 cin=None, cout=None,cerr=None, input_func=None):
82 """! Initializer
83
84 @param self: this object
85 @param argv: Command line options for IPython
86 @param user_ns: User namespace.
87 @param user_global_ns: User global namespace.
88 @param cin: Console standard input.
89 @param cout: Console standard output.
90 @param cerr: Console standard error.
91 @param input_func: Replacement for builtin raw_input()
92 @return none
93 """
94 io = IPython.utils.io
95 if input_func:
96 if parse_version(IPython.release.version) >= parse_version("1.2.1"):
97 IPython.terminal.interactiveshell.raw_input_original = input_func
98 else:
99 IPython.frontend.terminal.interactiveshell.raw_input_original = input_func
100 if cin:
101 io.stdin = io.IOStream(cin)
102 if cout:
103 io.stdout = io.IOStream(cout)
104 if cerr:
105 io.stderr = io.IOStream(cerr)
106
107 # This is to get rid of the blockage that occurs during
108 # IPython.Shell.InteractiveShell.user_setup()
109
110 io.raw_input = lambda x: None
111
112 os.environ['TERM'] = 'dumb'
113 excepthook = sys.excepthook
114
115 from IPython.config.loader import Config
116 cfg = Config()
117 cfg.InteractiveShell.colors = "Linux"
118
119 # InteractiveShell's __init__ overwrites io.stdout,io.stderr with
120 # sys.stdout, sys.stderr, this makes sure they are right
121 #
122 old_stdout, old_stderr = sys.stdout, sys.stderr
123 sys.stdout, sys.stderr = io.stdout.stream, io.stderr.stream
124
125 # InteractiveShell inherits from SingletonConfigurable, so use instance()
126 #
127 if parse_version(IPython.release.version) >= parse_version("1.2.1"):
128 self.IP = IPython.terminal.embed.InteractiveShellEmbed.instance(\
129 config=cfg, user_ns=user_ns)
130 else:
131 self.IP = IPython.frontend.terminal.embed.InteractiveShellEmbed.instance(\
132 config=cfg, user_ns=user_ns)
133
134 sys.stdout, sys.stderr = old_stdout, old_stderr
135
136 self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd),
137 header='IPython system call: ')
138 # local_ns=user_ns)
139 #global_ns=user_global_ns)
140 #verbose=self.IP.rc.system_verbose)
141
142 self.IP.raw_input = input_func
143 sys.excepthook = excepthook
144 self.iter_more = 0
145 self.history_level = 0
146 self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
147 self.updateNamespace({'exit':lambda:None})
148 self.updateNamespace({'quit':lambda:None})
149 if parse_version(IPython.release.version) < parse_version("5.0.0"):
150 self.IP.readline_startup_hook(self.IP.pre_readline)
151 # Workaround for updating namespace with sys.modules
152 #
153 self.__update_namespace()
154
155 def __update_namespace(self):
156 """!
157 Update self.IP namespace for autocompletion with sys.modules
158 """
159 for k, v in list(sys.modules.items()):
160 if not '.' in k:
161 self.IP.user_ns.update({k:v})
162
163 def execute(self):
164 """!
165 Executes the current line provided by the shell object.
166 """
167 self.history_level = 0
168 orig_stdout = sys.stdout
169 sys.stdout = IPython.utils.io.stdout
170
171 orig_stdin = sys.stdin
172 sys.stdin = IPython.utils.io.stdin;
173 self.prompt = self.generatePrompt(self.iter_more)
174
175 self.IP.hooks.pre_prompt_hook()
176 if self.iter_more:
177 try:
178 self.prompt = self.generatePrompt(True)
179 except:
180 self.IP.showtraceback()
181 if self.IP.autoindent:
182 self.IP.rl_do_indent = True
183
184 try:
185 line = self.IP.raw_input(self.prompt)
186 except KeyboardInterrupt:
187 self.IP.write('\nKeyboardInterrupt\n')
188 self.IP.input_splitter.reset()
189 except:
190 self.IP.showtraceback()
191 else:
192 self.IP.input_splitter.push(line)
193 self.iter_more = self.IP.input_splitter.push_accepts_more()
194 self.prompt = self.generatePrompt(self.iter_more)
195 if (self.IP.SyntaxTB.last_syntax_error and
196 self.IP.autoedit_syntax):
197 self.IP.edit_syntax_error()
198 if not self.iter_more:
199 if parse_version(IPython.release.version) >= parse_version("2.0.0-dev"):
200 source_raw = self.IP.input_splitter.raw_reset()
201 else:
202 source_raw = self.IP.input_splitter.source_raw_reset()[1]
203 self.IP.run_cell(source_raw, store_history=True)
204 self.IP.rl_do_indent = False
205 else:
206 # TODO: Auto-indent
207 #
208 self.IP.rl_do_indent = True
209 pass
210
211 sys.stdout = orig_stdout
212 sys.stdin = orig_stdin
213
214 def generatePrompt(self, is_continuation):
215 """!
216 Generate prompt depending on is_continuation value
217
218 @param is_continuation
219 @return: The prompt string representation
220
221 """
222
223 # Backwards compatibility with ipyton-0.11
224 #
225 ver = IPython.__version__
226 if ver[0:4] == '0.11':
227 prompt = self.IP.hooks.generate_prompt(is_continuation)
228 elif parse_version(IPython.release.version) < parse_version("5.0.0"):
229 if is_continuation:
230 prompt = self.IP.prompt_manager.render('in2')
231 else:
232 prompt = self.IP.prompt_manager.render('in')
233 else:
234 # TODO: update to IPython 5.x and later
235 prompt = "In [%d]: " % self.IP.execution_count
236
237 return prompt
238
239
240 def historyBack(self):
241 """!
242 Provides one history command back.
243
244 @param self this object
245 @return: The command string.
246 """
247 self.history_level -= 1
248 if not self._getHistory():
249 self.history_level +=1
250 return self._getHistory()
251
252 def historyForward(self):
253 """!
254 Provides one history command forward.
255
256 @param self this object
257 @return: The command string.
258 """
259 if self.history_level < 0:
260 self.history_level += 1
261 return self._getHistory()
262
263 def _getHistory(self):
264 """!
265 Get's the command string of the current history level.
266
267 @param self this object
268 @return: Historic command string.
269 """
270 try:
271 rv = self.IP.user_ns['In'][self.history_level].strip('\n')
272 except IndexError:
273 rv = ''
274 return rv
275
276 def updateNamespace(self, ns_dict):
277 """!
278 Add the current dictionary to the shell namespace.
279
280 @param ns_dict: A dictionary of symbol-values.
281 @return none
282 """
283 self.IP.user_ns.update(ns_dict)
284
285 def complete(self, line):
286 """!
287 Returns an auto completed line and/or posibilities for completion.
288
289 @param line: Given line so far.
290 @return: Line completed as for as possible, and possible further completions.
291 """
292 split_line = self.complete_sep.split(line)
293 if split_line[-1]:
294 possibilities = self.IP.complete(split_line[-1])
295 else:
296 completed = line
297 possibilities = ['', []]
298 if possibilities:
299 def _commonPrefix(str1, str2):
300 """!
301 Reduction function. returns common prefix of two given strings.
302
303 @param str1: First string.
304 @param str2: Second string
305 @return: Common prefix to both strings.
306 """
307 for i in range(len(str1)):
308 if not str2.startswith(str1[:i+1]):
309 return str1[:i]
310 return str1
311 if possibilities[1]:
312 common_prefix = reduce(_commonPrefix, possibilities[1]) or line[-1]
313 completed = line[:-len(split_line[-1])]+common_prefix
314 else:
315 completed = line
316 else:
317 completed = line
318 return completed, possibilities[1]
319
320
321 def shell(self, cmd,verbose=0,debug=0,header=''):
322 """!
323 Replacement method to allow shell commands without them blocking.
324
325 @param cmd: Shell command to execute.
326 @param verbose: Verbosity
327 @param debug: Debug level
328 @param header: Header to be printed before output
329 @return none
330 """
331 stat = 0
332 if verbose or debug: print header+cmd
333 # flush stdout so we don't mangle python's buffering
334 if not debug:
335 input, output = os.popen4(cmd)
336 print output.read()
337 output.close()
338 input.close()
339
340 ## ConsoleView class
341 class ConsoleView(gtk.TextView):
342 ## @var ANSI_COLORS
343 # color list
344 ## @var text_buffer
345 # text buffer
346 ## @var mark
347 # scroll mark
348 ## @var color_pat
349 # color pattern
350 ## @var line_start
351 # line start
352 """
353 Specialized text view for console-like workflow.
354
355 @cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
356 @type ANSI_COLORS: dictionary
357
358 @ivar text_buffer: Widget's text buffer.
359 @type text_buffer: gtk.TextBuffer
360 @ivar color_pat: Regex of terminal color pattern
361 @type color_pat: _sre.SRE_Pattern
362 @ivar mark: Scroll mark for automatic scrolling on input.
363 @type mark: gtk.TextMark
364 @ivar line_start: Start of command line mark.
365 @type line_start: gtk.TextMark
366 """
367 ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red',
368 '0;32': 'Green', '0;33': 'Brown',
369 '0;34': 'Blue', '0;35': 'Purple',
370 '0;36': 'Cyan', '0;37': 'LightGray',
371 '1;30': 'DarkGray', '1;31': 'DarkRed',
372 '1;32': 'SeaGreen', '1;33': 'Yellow',
373 '1;34': 'LightBlue', '1;35': 'MediumPurple',
374 '1;36': 'LightCyan', '1;37': 'White'}
375
376 def __init__(self):
377 """
378 Initialize console view.
379 """
380 gtk.TextView.__init__(self)
381 self.modify_font(pango.FontDescription('Mono'))
382 self.set_cursor_visible(True)
383 self.text_buffer = self.get_buffer()
384 self.mark = self.text_buffer.create_mark('scroll_mark',
385 self.text_buffer.get_end_iter(),
386 False)
387 for code in self.ANSI_COLORS:
388 self.text_buffer.create_tag(code,
389 foreground=self.ANSI_COLORS[code],
390 weight=700)
391 self.text_buffer.create_tag('0')
392 self.text_buffer.create_tag('notouch', editable=False)
393 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
394 self.line_start = \
395 self.text_buffer.create_mark('line_start',
396 self.text_buffer.get_end_iter(), True)
397 self.connect('key-press-event', self.onKeyPress)
398
399 def write(self, text, editable=False):
400 """!
401 Write given text to buffer.
402
403 @param text: Text to append.
404 @param editable: If true, added text is editable.
405 @return none
406 """
407 gobject.idle_add(self._write, text, editable)
408
409 def _write(self, text, editable=False):
410 """!
411 Write given text to buffer.
412
413 @param text: Text to append.
414 @param editable: If true, added text is editable.
415 @return none
416 """
417 segments = self.color_pat.split(text)
418 segment = segments.pop(0)
419 start_mark = self.text_buffer.create_mark(None,
420 self.text_buffer.get_end_iter(),
421 True)
422 self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)
423
424 if segments:
425 ansi_tags = self.color_pat.findall(text)
426 for tag in ansi_tags:
427 i = segments.index(tag)
428 self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(),
429 segments[i+1], str(tag))
430 segments.pop(i)
431 if not editable:
432 self.text_buffer.apply_tag_by_name('notouch',
433 self.text_buffer.get_iter_at_mark(start_mark),
434 self.text_buffer.get_end_iter())
435 self.text_buffer.delete_mark(start_mark)
436 self.scroll_mark_onscreen(self.mark)
437
438 def showPrompt(self, prompt):
439 """!
440 Prints prompt at start of line.
441
442 @param prompt: Prompt to print.
443 @return none
444 """
445 gobject.idle_add(self._showPrompt, prompt)
446
447 def _showPrompt(self, prompt):
448 """!
449 Prints prompt at start of line.
450
451 @param prompt: Prompt to print.
452 @return none
453 """
454 self._write(prompt)
455 self.text_buffer.move_mark(self.line_start,
456 self.text_buffer.get_end_iter())
457
458 def changeLine(self, text):
459 """!
460 Replace currently entered command line with given text.
461
462 @param text: Text to use as replacement.
463 @return none
464 """
465 gobject.idle_add(self._changeLine, text)
466
467 def _changeLine(self, text):
468 """!
469 Replace currently entered command line with given text.
470
471 @param text: Text to use as replacement.
472 @return none
473 """
474 iter = self.text_buffer.get_iter_at_mark(self.line_start)
475 iter.forward_to_line_end()
476 self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter)
477 self._write(text, True)
478
479 def getCurrentLine(self):
480 """!
481 Get text in current command line.
482
483 @return Text of current command line.
484 """
485 rv = self.text_buffer.get_slice(
486 self.text_buffer.get_iter_at_mark(self.line_start),
487 self.text_buffer.get_end_iter(), False)
488 return rv
489
490 def showReturned(self, text):
491 """!
492 Show returned text from last command and print new prompt.
493
494 @param text: Text to show.
495 @return none
496 """
497 gobject.idle_add(self._showReturned, text)
498
499 def _showReturned(self, text):
500 """!
501 Show returned text from last command and print new prompt.
502
503 @param text: Text to show.
504 @return none
505 """
506 iter = self.text_buffer.get_iter_at_mark(self.line_start)
507 iter.forward_to_line_end()
508 self.text_buffer.apply_tag_by_name(
509 'notouch',
510 self.text_buffer.get_iter_at_mark(self.line_start),
511 iter)
512 self._write('\n'+text)
513 if text:
514 self._write('\n')
515 self._showPrompt(self.prompt)
516 self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter())
517 self.text_buffer.place_cursor(self.text_buffer.get_end_iter())
518
519 if self.IP.rl_do_indent:
520 indentation = self.IP.input_splitter.indent_spaces * ' '
521 self.text_buffer.insert_at_cursor(indentation)
522
523 def onKeyPress(self, widget, event):
524 """!
525 Key press callback used for correcting behavior for console-like
526 interfaces. For example 'home' should go to prompt, not to begining of
527 line.
528
529 @param widget: Widget that key press accored in.
530 @param event: Event object
531 @return Return True if event should not trickle.
532 """
533 insert_mark = self.text_buffer.get_insert()
534 insert_iter = self.text_buffer.get_iter_at_mark(insert_mark)
535 selection_mark = self.text_buffer.get_selection_bound()
536 selection_iter = self.text_buffer.get_iter_at_mark(selection_mark)
537 start_iter = self.text_buffer.get_iter_at_mark(self.line_start)
538 if event.keyval == gtk.keysyms.Home:
539 if event.state & gtk.gdk.CONTROL_MASK or event.state & gtk.gdk.MOD1_MASK:
540 pass
541 elif event.state & gtk.gdk.SHIFT_MASK:
542 self.text_buffer.move_mark(insert_mark, start_iter)
543 return True
544 else:
545 self.text_buffer.place_cursor(start_iter)
546 return True
547 elif event.keyval == gtk.keysyms.Left:
548 insert_iter.backward_cursor_position()
549 if not insert_iter.editable(True):
550 return True
551 elif not event.string:
552 pass
553 elif start_iter.compare(insert_iter) <= 0 and \
554 start_iter.compare(selection_iter) <= 0:
555 pass
556 elif start_iter.compare(insert_iter) > 0 and \
557 start_iter.compare(selection_iter) > 0:
558 self.text_buffer.place_cursor(start_iter)
559 elif insert_iter.compare(selection_iter) < 0:
560 self.text_buffer.move_mark(insert_mark, start_iter)
561 elif insert_iter.compare(selection_iter) > 0:
562 self.text_buffer.move_mark(selection_mark, start_iter)
563
564 return self.onKeyPressExtend(event)
565
566 def onKeyPressExtend(self, event):
567 """!
568 For some reason we can't extend onKeyPress directly (bug #500900).
569 @param event key press
570 @return none
571 """
572 pass
573
574 ## IPythonView class
575 class IPythonView(ConsoleView, IterableIPShell):
576 ## @var cout
577 # cout
578 ## @var interrupt
579 # interrupt
580 ## @var execute
581 # execute
582 ## @var prompt
583 # prompt
584 ## @var showPrompt
585 # show prompt
586 ## @var history_pos
587 # history list
588 ## @var window
589 # GTK Window
590 """
591 Sub-class of both modified IPython shell and L{ConsoleView} this makes
592 a GTK+ IPython console.
593 """
594 def __init__(self):
595 """
596 Initialize. Redirect I/O to console.
597 """
598 ConsoleView.__init__(self)
599 self.cout = StringIO()
600 IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout,
601 input_func=self.raw_input)
602 self.interrupt = False
603 self.execute()
604 self.prompt = self.generatePrompt(False)
605 self.cout.truncate(0)
606 self.showPrompt(self.prompt)
607
608 def raw_input(self, prompt=''):
609 """!
610 Custom raw_input() replacement. Get's current line from console buffer.
611
612 @param prompt: Prompt to print. Here for compatability as replacement.
613 @return The current command line text.
614 """
615 if self.interrupt:
616 self.interrupt = False
617 raise KeyboardInterrupt
618 return self.getCurrentLine()
619
620 def onKeyPressExtend(self, event):
621 """!
622 Key press callback with plenty of shell goodness, like history,
623 autocompletions, etc.
624
625 @param event: Event object.
626 @return True if event should not trickle.
627 """
628
629 if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99:
630 self.interrupt = True
631 self._processLine()
632 return True
633 elif event.keyval == gtk.keysyms.Return:
634 self._processLine()
635 return True
636 elif event.keyval == gtk.keysyms.Up:
637 self.changeLine(self.historyBack())
638 return True
639 elif event.keyval == gtk.keysyms.Down:
640 self.changeLine(self.historyForward())
641 return True
642 elif event.keyval == gtk.keysyms.Tab:
643 if not self.getCurrentLine().strip():
644 return False
645 completed, possibilities = self.complete(self.getCurrentLine())
646 if len(possibilities) > 1:
647 slice = self.getCurrentLine()
648 self.write('\n')
649 for symbol in possibilities:
650 self.write(symbol+'\n')
651 self.showPrompt(self.prompt)
652 self.changeLine(completed or slice)
653 return True
654
655 def _processLine(self):
656 """!
657 Process current command line.
658 @return none
659 """
660 self.history_pos = 0
661 self.execute()
662 rv = self.cout.getvalue()
663 if rv: rv = rv.strip('\n')
664 self.showReturned(rv)
665 self.cout.truncate(0)
666 self.cout.seek(0)
667
668 if __name__ == "__main__":
669 window = gtk.Window()
670 window.set_default_size(640, 320)
671 window.connect('delete-event', lambda x, y: gtk.main_quit())
672 window.add(IPythonView())
673 window.show_all()
674 gtk.main()
675

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