3
Provides IPython console widget.
5
@author: Eitan Isaacson
6
@organization: IBM Corporation
7
@copyright: Copyright (c) 2007 IBM Corporation
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}
20
from StringIO import StringIO
28
class IterableIPShell:
30
Create an IPython instance. Does not start a blocking event loop,
31
instead allow single iterations. This allows embedding in GTK+
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
45
def __init__(self,argv=[],user_ns=None,user_global_ns=None,
46
cin=None, cout=None,cerr=None, input_func=None):
50
@param argv: Command line options for IPython
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.
58
@param cout: Console standard output.
60
@param cerr: Console standard error.
62
@param input_func: Replacement for builtin raw_input()
63
@type input_func: function
66
IPython.iplib.raw_input_original = input_func
68
IPython.Shell.Term.cin = cin
70
IPython.Shell.Term.cout = cout
72
IPython.Shell.Term.cerr = cerr
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
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(
83
user_global_ns=user_global_ns,
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
91
self.history_level = 0
92
self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
96
Executes the current line provided by the shell object.
98
self.history_level = 0
99
orig_stdout = sys.stdout
100
sys.stdout = IPython.Shell.Term.cout
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
111
if self.IP.autoindent:
112
self.IP.indent_current_nsp = 0
115
self.IP.showtraceback()
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()
122
self.prompt = str(self.IP.outputcache.prompt2).strip()
123
if self.IP.autoindent:
124
self.IP.readline_startup_hook(self.IP.pre_readline)
126
self.prompt = str(self.IP.outputcache.prompt1).strip()
127
sys.stdout = orig_stdout
129
def historyBack(self):
131
Provides one history command back.
133
@return: The command string.
136
self.history_level -= 1
137
return self._getHistory()
139
def historyForward(self):
141
Provides one history command forward.
143
@return: The command string.
146
self.history_level += 1
147
return self._getHistory()
149
def _getHistory(self):
151
Get's the command string of the current history level.
153
@return: Historic command string.
157
rv = self.IP.user_ns['In'][self.history_level].strip('\n')
159
self.history_level = 0
163
def updateNamespace(self, ns_dict):
165
Add the current dictionary to the shell namespace.
167
@param ns_dict: A dictionary of symbol-values.
168
@type ns_dict: dictionary
170
self.IP.user_ns.update(ns_dict)
172
def complete(self, line):
174
Returns an auto completed line and/or posibilities for completion.
176
@param line: Given line so far.
179
@return: Line completed as for as possible,
180
and possible further completions.
183
split_line = self.complete_sep.split(line)
184
possibilities = self.IP.complete(split_line[-1])
186
def _commonPrefix(str1, str2):
188
Reduction function. returns common prefix of two given strings.
190
@param str1: First string.
192
@param str2: Second string
195
@return: Common prefix to both strings.
198
for i in range(len(str1)):
199
if not str2.startswith(str1[:i+1]):
202
common_prefix = reduce(_commonPrefix, possibilities)
203
completed = line[:-len(split_line[-1])]+common_prefix
206
return completed, possibilities
209
def shell(self, cmd,verbose=0,debug=0,header=''):
211
Replacement method to allow shell commands without them blocking.
213
@param cmd: Shell command to execute.
215
@param verbose: Verbosity
216
@type verbose: integer
217
@param debug: Debug level
219
@param header: Header to be printed before output
223
if verbose or debug: print header+cmd
224
# flush stdout so we don't mangle python's buffering
226
input, output = os.popen4(cmd)
231
class ConsoleView(gtk.TextView):
233
Specialized text view for console-like workflow.
235
@cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
236
@type ANSI_COLORS: dictionary
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
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'}
258
Initialize console view.
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(),
267
for code in self.ANSI_COLORS:
268
self.text_buffer.create_tag(code,
269
foreground=self.ANSI_COLORS[code],
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?')
275
self.text_buffer.create_mark('line_start',
276
self.text_buffer.get_end_iter(), True)
277
self.connect('key-press-event', self.onKeyPress)
279
def write(self, text, editable=False):
280
gobject.idle_add(self._write, text, editable)
282
def _write(self, text, editable=False):
284
Write given text to buffer.
286
@param text: Text to append.
288
@param editable: If true, added text is editable.
289
@type editable: boolean
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(),
296
self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)
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(),
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)
313
def showPrompt(self, prompt):
314
gobject.idle_add(self._showPrompt, prompt)
316
def _showPrompt(self, prompt):
318
Prints prompt at start of line.
320
@param prompt: Prompt to print.
324
self.text_buffer.move_mark(self.line_start,
325
self.text_buffer.get_end_iter())
327
def changeLine(self, text):
328
gobject.idle_add(self._changeLine, text)
330
def _changeLine(self, text):
332
Replace currently entered command line with given text.
334
@param text: Text to use as replacement.
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)
342
def getCurrentLine(self):
344
Get text in current command line.
346
@return: Text of current command line.
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)
354
def showReturned(self, text):
355
gobject.idle_add(self._showReturned, text)
357
def _showReturned(self, text):
359
Show returned text from last command and print new prompt.
361
@param text: Text to show.
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(
368
self.text_buffer.get_iter_at_mark(self.line_start),
371
self._write('\n'+text)
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())
380
def onKeyPress(self, widget, event):
382
Key press callback used for correcting behavior for console-like
383
interfaces. For example 'home' should go to prompt, not to begining of
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
391
@return: Return True if event should not trickle.
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:
402
elif event.state & gtk.gdk.SHIFT_MASK:
403
self.text_buffer.move_mark(insert_mark, start_iter)
406
self.text_buffer.place_cursor(start_iter)
408
elif event.keyval == gtk.keysyms.Left:
409
insert_iter.backward_cursor_position()
410
if not insert_iter.editable(True):
412
elif not event.string:
414
elif start_iter.compare(insert_iter) <= 0 and \
415
start_iter.compare(selection_iter) <= 0:
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)
425
return self.onKeyPressExtend(event)
427
def onKeyPressExtend(self, event):
429
For some reason we can't extend onKeyPress directly (bug #500900).
433
class IPythonView(ConsoleView, IterableIPShell):
435
Sub-class of both modified IPython shell and L{ConsoleView} this makes
436
a GTK+ IPython console.
440
Initialize. Redirect I/O to console.
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)
448
self.cout.truncate(0)
449
self.showPrompt(self.prompt)
450
self.interrupt = False
452
def raw_input(self, prompt=''):
454
Custom raw_input() replacement. Get's current line from console buffer.
456
@param prompt: Prompt to print. Here for compatability as replacement.
459
@return: The current command line text.
463
self.interrupt = False
464
raise KeyboardInterrupt
465
return self.getCurrentLine()
467
def onKeyPressExtend(self, event):
469
Key press callback with plenty of shell goodness, like history,
470
autocompletions, etc.
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
477
@return: True if event should not trickle.
480
if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99:
481
self.interrupt = True
484
elif event.keyval == gtk.keysyms.Return:
487
elif event.keyval == gtk.keysyms.Up:
488
self.changeLine(self.historyBack())
490
elif event.keyval == gtk.keysyms.Down:
491
self.changeLine(self.historyForward())
493
elif event.keyval == gtk.keysyms.Tab:
494
if not self.getCurrentLine().strip():
496
completed, possibilities = self.complete(self.getCurrentLine())
497
if len(possibilities) > 1:
498
slice = self.getCurrentLine()
500
for symbol in possibilities:
501
self.write(symbol+'\n')
502
self.showPrompt(self.prompt)
503
self.changeLine(completed or slice)
506
def _processLine(self):
508
Process current command line.
512
rv = self.cout.getvalue()
513
if rv: rv = rv.strip('\n')
514
self.showReturned(rv)
515
self.cout.truncate(0)
517
if __name__ == '__main__':
519
sw = gtk.ScrolledWindow()
521
sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
526
v.set_wrap_mode(gtk.WRAP_CHAR)