5 ## Copyright (C) 2008-2010 Yann Leboulanger <asterix AT lagaule.org>
7 ## This file is part of Gajim.
9 ## Gajim is free software; you can redistribute it and/or modify
10 ## it under the terms of the GNU General Public License as published
11 ## by the Free Software Foundation; version 3 only.
13 ## Gajim is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ## GNU General Public License for more details.
18 ## You should have received a copy of the GNU General Public License
19 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
21 ## Copyright (c) 2007, IBM Corporation
22 ## All rights reserved.
24 ## Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
26 ## * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
27 ## * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
28 ## * Neither the name of the IBM Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
30 ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 Provides IPython console widget
35 @author: Eitan Isaacson
36 @organization: IBM Corporation
37 @copyright: Copyright (c) 2007 IBM Corporation
40 All rights reserved. This program and the accompanying materials are made
41 available under the terms of the BSD which accompanies this distribution, and
42 is available at U{http://www.opensource.org/licenses/bsd-license.php}
50 from StringIO
import StringIO
55 import IPython
.prefilter
57 import IPython
.genutils
58 import IPython
.shadowns
59 import IPython
.history
63 class IterableIPShell
:
65 Create an IPython instance. Does not start a blocking event loop,
66 instead allow single iterations. This allows embedding in GTK+
69 @ivar IP: IPython instance.
70 @type IP: IPython.iplib.InteractiveShell
71 @ivar iter_more: Indicates if the line executed was a complete command,
72 or we should wait for more.
73 @type iter_more: integer
74 @ivar history_level: The place in history where we currently are
75 when pressing up/down.
76 @type history_level: integer
77 @ivar complete_sep: Seperation delimeters for completion function.
78 @type complete_sep: _sre.SRE_Pattern
80 def __init__(self
,argv
=[],user_ns
=None,user_global_ns
=None, cin
=None,
81 cout
=None,cerr
=None, input_func
=None):
83 @param argv: Command line options for IPython
85 @param user_ns: User namespace.
86 @type user_ns: dictionary
87 @param user_global_ns: User global namespace.
88 @type user_global_ns: dictionary.
89 @param cin: Console standard input.
91 @param cout: Console standard output.
93 @param cerr: Console standard error.
95 @param input_func: Replacement for builtin raw_input()
96 @type input_func: function
99 IPython
.iplib
.raw_input_original
= input_func
101 IPython
.Shell
.Term
.cin
= cin
103 IPython
.Shell
.Term
.cout
= cout
105 IPython
.Shell
.Term
.cerr
= cerr
107 # This is to get rid of the blockage that accurs during
108 # IPython.Shell.InteractiveShell.user_setup()
109 IPython
.iplib
.raw_input = lambda x
: None
111 self
.term
= IPython
.genutils
.IOTerm(cin
=cin
, cout
=cout
, cerr
=cerr
)
112 os
.environ
['TERM'] = 'dumb'
113 excepthook
= sys
.excepthook
114 self
.IP
= IPython
.Shell
.make_IPython(
115 argv
, user_ns
=user_ns
,
116 user_global_ns
=user_global_ns
,
118 shell_class
=IPython
.Shell
.InteractiveShell
)
119 self
.IP
.system
= lambda cmd
: self
.shell(self
.IP
.var_expand(cmd
),
120 header
='IPython system call: ',
121 verbose
=self
.IP
.rc
.system_verbose
)
122 sys
.excepthook
= excepthook
124 self
.history_level
= 0
125 self
.complete_sep
= re
.compile('[\s\{\}\[\]\(\)]')
129 Execute the current line provided by the shell object
131 self
.history_level
= 0
132 orig_stdout
= sys
.stdout
133 sys
.stdout
= IPython
.Shell
.Term
.cout
135 line
= self
.IP
.raw_input(None, self
.iter_more
)
136 if self
.IP
.autoindent
:
137 self
.IP
.readline_startup_hook(None)
138 except KeyboardInterrupt:
139 self
.IP
.write('\nKeyboardInterrupt\n')
140 self
.IP
.resetbuffer()
141 # keep cache in sync with the prompt counter:
142 self
.IP
.outputcache
.prompt_count
-= 1
144 if self
.IP
.autoindent
:
145 self
.IP
.indent_current_nsp
= 0
148 self
.IP
.showtraceback()
150 self
.iter_more
= self
.IP
.push(line
)
151 if (self
.IP
.SyntaxTB
.last_syntax_error
and
152 self
.IP
.rc
.autoedit_syntax
):
153 self
.IP
.edit_syntax_error()
155 self
.prompt
= str(self
.IP
.outputcache
.prompt2
).strip()
156 if self
.IP
.autoindent
:
157 self
.IP
.readline_startup_hook(self
.IP
.pre_readline
)
159 self
.prompt
= str(self
.IP
.outputcache
.prompt1
).strip()
160 sys
.stdout
= orig_stdout
162 def historyBack(self
):
164 Provide one history command back
166 @return: The command string.
169 self
.history_level
-= 1
170 return self
._getHistory
()
172 def historyForward(self
):
174 Provide one history command forward
176 @return: The command string.
179 self
.history_level
+= 1
180 return self
._getHistory
()
182 def _getHistory(self
):
184 Get the command string of the current history level
186 @return: Historic command string.
190 rv
= self
.IP
.user_ns
['In'][self
.history_level
].strip('\n')
192 self
.history_level
= 0
196 def updateNamespace(self
, ns_dict
):
198 Add the current dictionary to the shell namespace
200 @param ns_dict: A dictionary of symbol-values.
201 @type ns_dict: dictionary
203 self
.IP
.user_ns
.update(ns_dict
)
205 def complete(self
, line
):
207 Returns an auto completed line and/or posibilities for completion
209 @param line: Given line so far.
212 @return: Line completed as for as possible,
213 and possible further completions.
216 split_line
= self
.complete_sep
.split(line
)
217 possibilities
= self
.IP
.complete(split_line
[-1])
221 except AttributeError:
223 for element
in iterable
:
228 def common_prefix(seq
):
230 Return the common prefix of a sequence of strings
232 return "".join(c
for i
, c
in enumerate(seq
[0])
233 if all(s
.startswith(c
, i
) for s
in seq
))
235 completed
= line
[:-len(split_line
[-1])]+common_prefix(possibilities
)
238 return completed
, possibilities
241 def shell(self
, cmd
,verbose
=0,debug
=0,header
=''):
243 Replacement method to allow shell commands without them blocking
245 @param cmd: Shell command to execute.
247 @param verbose: Verbosity
248 @type verbose: integer
249 @param debug: Debug level
251 @param header: Header to be printed before output
254 if verbose
or debug
: print header
+cmd
255 # flush stdout so we don't mangle python's buffering
257 input_
, output
= os
.popen4(cmd
)
262 class ConsoleView(gtk
.TextView
):
264 Specialized text view for console-like workflow
266 @cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
267 @type ANSI_COLORS: dictionary
269 @ivar text_buffer: Widget's text buffer.
270 @type text_buffer: gtk.TextBuffer
271 @ivar color_pat: Regex of terminal color pattern
272 @type color_pat: _sre.SRE_Pattern
273 @ivar mark: Scroll mark for automatic scrolling on input.
274 @type mark: gtk.TextMark
275 @ivar line_start: Start of command line mark.
276 @type line_start: gtk.TextMark
279 ANSI_COLORS
= {'0;30': 'Black', '0;31': 'Red',
280 '0;32': 'Green', '0;33': 'Brown',
281 '0;34': 'Blue', '0;35': 'Purple',
282 '0;36': 'Cyan', '0;37': 'LightGray',
283 '1;30': 'DarkGray', '1;31': 'DarkRed',
284 '1;32': 'SeaGreen', '1;33': 'Yellow',
285 '1;34': 'LightBlue', '1;35': 'MediumPurple',
286 '1;36': 'LightCyan', '1;37': 'White'}
290 Initialize console view
292 gtk
.TextView
.__init
__(self
)
293 self
.modify_font(pango
.FontDescription('Mono'))
294 self
.set_cursor_visible(True)
295 self
.text_buffer
= self
.get_buffer()
296 self
.mark
= self
.text_buffer
.create_mark('scroll_mark',
297 self
.text_buffer
.get_end_iter(),
299 for code
in self
.ANSI_COLORS
:
300 self
.text_buffer
.create_tag(code
,
301 foreground
=self
.ANSI_COLORS
[code
],
303 self
.text_buffer
.create_tag('0')
304 self
.text_buffer
.create_tag('notouch', editable
=False)
305 self
.color_pat
= re
.compile('\x01?\x1b\[(.*?)m\x02?')
307 self
.text_buffer
.create_mark('line_start',
308 self
.text_buffer
.get_end_iter(), True)
309 self
.connect('key-press-event', self
.onKeyPress
)
311 def write(self
, text
, editable
=False):
312 gobject
.idle_add(self
._write
, text
, editable
)
314 def _write(self
, text
, editable
=False):
316 Write given text to buffer
318 @param text: Text to append.
320 @param editable: If true, added text is editable.
321 @type editable: boolean
323 segments
= self
.color_pat
.split(text
)
324 segment
= segments
.pop(0)
325 start_mark
= self
.text_buffer
.create_mark(None,
326 self
.text_buffer
.get_end_iter(),
328 self
.text_buffer
.insert(self
.text_buffer
.get_end_iter(), segment
)
331 ansi_tags
= self
.color_pat
.findall(text
)
332 for tag
in ansi_tags
:
333 i
= segments
.index(tag
)
334 self
.text_buffer
.insert_with_tags_by_name(self
.text_buffer
.get_end_iter(),
338 self
.text_buffer
.apply_tag_by_name('notouch',
339 self
.text_buffer
.get_iter_at_mark(start_mark
),
340 self
.text_buffer
.get_end_iter())
341 self
.text_buffer
.delete_mark(start_mark
)
342 self
.scroll_mark_onscreen(self
.mark
)
345 def showPrompt(self
, prompt
):
346 gobject
.idle_add(self
._showPrompt
, prompt
)
348 def _showPrompt(self
, prompt
):
350 Print prompt at start of line
352 @param prompt: Prompt to print.
356 self
.text_buffer
.move_mark(self
.line_start
,
357 self
.text_buffer
.get_end_iter())
359 def changeLine(self
, text
):
360 gobject
.idle_add(self
._changeLine
, text
)
362 def _changeLine(self
, text
):
364 Replace currently entered command line with given text
366 @param text: Text to use as replacement.
369 iter_
= self
.text_buffer
.get_iter_at_mark(self
.line_start
)
370 iter_
.forward_to_line_end()
371 self
.text_buffer
.delete(self
.text_buffer
.get_iter_at_mark(self
.line_start
), iter_
)
372 self
._write
(text
, True)
374 def getCurrentLine(self
):
376 Get text in current command line
378 @return: Text of current command line.
381 rv
= self
.text_buffer
.get_slice(
382 self
.text_buffer
.get_iter_at_mark(self
.line_start
),
383 self
.text_buffer
.get_end_iter(), False)
386 def showReturned(self
, text
):
387 gobject
.idle_add(self
._showReturned
, text
)
389 def _showReturned(self
, text
):
391 Show returned text from last command and print new prompt
393 @param text: Text to show.
396 iter_
= self
.text_buffer
.get_iter_at_mark(self
.line_start
)
397 iter_
.forward_to_line_end()
398 self
.text_buffer
.apply_tag_by_name(
400 self
.text_buffer
.get_iter_at_mark(self
.line_start
),
402 self
._write
('\n'+text
)
405 self
._showPrompt
(self
.prompt
)
406 self
.text_buffer
.move_mark(self
.line_start
, self
.text_buffer
.get_end_iter())
407 self
.text_buffer
.place_cursor(self
.text_buffer
.get_end_iter())
409 def onKeyPress(self
, widget
, event
):
411 Key press callback used for correcting behavior for console-like
412 interfaces. For example 'home' should go to prompt, not to begining of
415 @param widget: Widget that key press accored in.
416 @type widget: gtk.Widget
417 @param event: Event object
418 @type event: gtk.gdk.Event
420 @return: Return True if event should not trickle.
423 insert_mark
= self
.text_buffer
.get_insert()
424 insert_iter
= self
.text_buffer
.get_iter_at_mark(insert_mark
)
425 selection_mark
= self
.text_buffer
.get_selection_bound()
426 selection_iter
= self
.text_buffer
.get_iter_at_mark(selection_mark
)
427 start_iter
= self
.text_buffer
.get_iter_at_mark(self
.line_start
)
428 if event
.keyval
== gtk
.keysyms
.Home
:
430 self
.text_buffer
.place_cursor(start_iter
)
432 elif event
.state
== gtk
.gdk
.SHIFT_MASK
:
433 self
.text_buffer
.move_mark(insert_mark
, start_iter
)
435 elif event
.keyval
== gtk
.keysyms
.Left
:
436 insert_iter
.backward_cursor_position()
437 if not insert_iter
.editable(True):
439 elif not event
.string
:
441 elif start_iter
.compare(insert_iter
) <= 0 and \
442 start_iter
.compare(selection_iter
) <= 0:
444 elif start_iter
.compare(insert_iter
) > 0 and \
445 start_iter
.compare(selection_iter
) > 0:
446 self
.text_buffer
.place_cursor(start_iter
)
447 elif insert_iter
.compare(selection_iter
) < 0:
448 self
.text_buffer
.move_mark(insert_mark
, start_iter
)
449 elif insert_iter
.compare(selection_iter
) > 0:
450 self
.text_buffer
.move_mark(selection_mark
, start_iter
)
452 return self
.onKeyPressExtend(event
)
454 def onKeyPressExtend(self
, event
):
456 For some reason we can't extend onKeyPress directly (bug #500900)
460 class IPythonView(ConsoleView
, IterableIPShell
):
462 Sub-class of both modified IPython shell and L{ConsoleView} this makes
463 a GTK+ IPython console.
467 Initialize. Redirect I/O to console
469 ConsoleView
.__init
__(self
)
470 self
.cout
= StringIO()
471 IterableIPShell
.__init
__(self
, cout
=self
.cout
, cerr
=self
.cout
,
472 input_func
=self
.raw_input)
473 # self.connect('key_press_event', self.keyPress)
475 self
.cout
.truncate(0)
476 self
.showPrompt(self
.prompt
)
477 self
.interrupt
= False
479 def raw_input(self
, prompt
=''):
481 Custom raw_input() replacement. Get's current line from console buffer
483 @param prompt: Prompt to print. Here for compatability as replacement.
486 @return: The current command line text.
490 self
.interrupt
= False
491 raise KeyboardInterrupt
492 return self
.getCurrentLine()
494 def onKeyPressExtend(self
, event
):
496 Key press callback with plenty of shell goodness, like history,
499 @param widget: Widget that key press occured in.
500 @type widget: gtk.Widget
501 @param event: Event object.
502 @type event: gtk.gdk.Event
504 @return: True if event should not trickle.
507 if event
.state
& gtk
.gdk
.CONTROL_MASK
and event
.keyval
== 99:
508 self
.interrupt
= True
511 elif event
.keyval
== gtk
.keysyms
.Return
:
514 elif event
.keyval
== gtk
.keysyms
.Up
:
515 self
.changeLine(self
.historyBack())
517 elif event
.keyval
== gtk
.keysyms
.Down
:
518 self
.changeLine(self
.historyForward())
520 elif event
.keyval
== gtk
.keysyms
.Tab
:
521 if not self
.getCurrentLine().strip():
523 completed
, possibilities
= self
.complete(self
.getCurrentLine())
524 if len(possibilities
) > 1:
525 slice_
= self
.getCurrentLine()
527 for symbol
in possibilities
:
528 self
.write(symbol
+'\n')
529 self
.showPrompt(self
.prompt
)
530 self
.changeLine(completed
or slice_
)
533 def _processLine(self
):
535 Process current command line
539 rv
= self
.cout
.getvalue()
540 if rv
: rv
= rv
.strip('\n')
541 self
.showReturned(rv
)
542 self
.cout
.truncate(0)