[kepi] ability to use subkeys. Fixes #6051
[gajim.git] / src / ipython_view.py
blob6ed52d23636961c9c65f4b95ce6ecb3c4268cd68
1 #!/usr/bin/python
2 # -*- coding:utf-8 -*-
3 ## src/ipython_view.py
4 ##
5 ## Copyright (C) 2008-2010 Yann Leboulanger <asterix AT lagaule.org>
6 ##
7 ## This file is part of Gajim.
8 ##
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.
32 """
33 Provides IPython console widget
35 @author: Eitan Isaacson
36 @organization: IBM Corporation
37 @copyright: Copyright (c) 2007 IBM Corporation
38 @license: BSD
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}
43 """
45 import gtk, gobject
46 import re
47 import sys
48 import os
49 import pango
50 from StringIO import StringIO
52 try:
53 import IPython
54 import IPython.iplib
55 import IPython.prefilter
56 import IPython.Shell
57 import IPython.genutils
58 import IPython.shadowns
59 import IPython.history
60 except ImportError:
61 IPython = None
63 class IterableIPShell:
64 """
65 Create an IPython instance. Does not start a blocking event loop,
66 instead allow single iterations. This allows embedding in GTK+
67 without blockage
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
79 """
80 def __init__(self,argv=[],user_ns=None,user_global_ns=None, cin=None,
81 cout=None,cerr=None, input_func=None):
82 """
83 @param argv: Command line options for IPython
84 @type argv: list
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.
90 @type cin: IO stream
91 @param cout: Console standard output.
92 @type cout: IO stream
93 @param cerr: Console standard error.
94 @type cerr: IO stream
95 @param input_func: Replacement for builtin raw_input()
96 @type input_func: function
97 """
98 if input_func:
99 IPython.iplib.raw_input_original = input_func
100 if cin:
101 IPython.Shell.Term.cin = cin
102 if cout:
103 IPython.Shell.Term.cout = cout
104 if cerr:
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,
117 embedded=True,
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
123 self.iter_more = 0
124 self.history_level = 0
125 self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
127 def execute(self):
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
134 try:
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
146 self.iter_more = 0
147 except:
148 self.IP.showtraceback()
149 else:
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()
154 if self.iter_more:
155 self.prompt = str(self.IP.outputcache.prompt2).strip()
156 if self.IP.autoindent:
157 self.IP.readline_startup_hook(self.IP.pre_readline)
158 else:
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.
167 @rtype: 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.
177 @rtype: 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.
187 @rtype: string
189 try:
190 rv = self.IP.user_ns['In'][self.history_level].strip('\n')
191 except IndexError:
192 self.history_level = 0
193 rv = ''
194 return rv
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.
210 @type line: string
212 @return: Line completed as for as possible,
213 and possible further completions.
214 @rtype: tuple
216 split_line = self.complete_sep.split(line)
217 possibilities = self.IP.complete(split_line[-1])
219 try:
220 __builtins__.all()
221 except AttributeError:
222 def all(iterable):
223 for element in iterable:
224 if not element:
225 return False
226 return True
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))
234 if possibilities:
235 completed = line[:-len(split_line[-1])]+common_prefix(possibilities)
236 else:
237 completed = line
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.
246 @type cmd: string
247 @param verbose: Verbosity
248 @type verbose: integer
249 @param debug: Debug level
250 @type debug: integer
251 @param header: Header to be printed before output
252 @type header: string
254 if verbose or debug: print header+cmd
255 # flush stdout so we don't mangle python's buffering
256 if not debug:
257 input_, output = os.popen4(cmd)
258 print output.read()
259 output.close()
260 input_.close()
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'}
288 def __init__(self):
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(),
298 False)
299 for code in self.ANSI_COLORS:
300 self.text_buffer.create_tag(code,
301 foreground=self.ANSI_COLORS[code],
302 weight=700)
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?')
306 self.line_start = \
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.
319 @type text: string
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(),
327 True)
328 self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)
330 if segments:
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(),
335 segments[i+1], tag)
336 segments.pop(i)
337 if not editable:
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.
353 @type prompt: string
355 self._write(prompt)
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.
367 @type text: string
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.
379 @rtype: string
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)
384 return rv
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.
394 @type text: string
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(
399 'notouch',
400 self.text_buffer.get_iter_at_mark(self.line_start),
401 iter_)
402 self._write('\n'+text)
403 if text:
404 self._write('\n')
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
413 line
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.
421 @rtype: boolean
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:
429 if event.state == 0:
430 self.text_buffer.place_cursor(start_iter)
431 return True
432 elif event.state == gtk.gdk.SHIFT_MASK:
433 self.text_buffer.move_mark(insert_mark, start_iter)
434 return True
435 elif event.keyval == gtk.keysyms.Left:
436 insert_iter.backward_cursor_position()
437 if not insert_iter.editable(True):
438 return True
439 elif not event.string:
440 pass
441 elif start_iter.compare(insert_iter) <= 0 and \
442 start_iter.compare(selection_iter) <= 0:
443 pass
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)
458 pass
460 class IPythonView(ConsoleView, IterableIPShell):
462 Sub-class of both modified IPython shell and L{ConsoleView} this makes
463 a GTK+ IPython console.
465 def __init__(self):
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)
474 self.execute()
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.
484 @type prompt: string
486 @return: The current command line text.
487 @rtype: string
489 if self.interrupt:
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,
497 autocompletions, etc
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.
505 @rtype: boolean
507 if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99:
508 self.interrupt = True
509 self._processLine()
510 return True
511 elif event.keyval == gtk.keysyms.Return:
512 self._processLine()
513 return True
514 elif event.keyval == gtk.keysyms.Up:
515 self.changeLine(self.historyBack())
516 return True
517 elif event.keyval == gtk.keysyms.Down:
518 self.changeLine(self.historyForward())
519 return True
520 elif event.keyval == gtk.keysyms.Tab:
521 if not self.getCurrentLine().strip():
522 return False
523 completed, possibilities = self.complete(self.getCurrentLine())
524 if len(possibilities) > 1:
525 slice_ = self.getCurrentLine()
526 self.write('\n')
527 for symbol in possibilities:
528 self.write(symbol+'\n')
529 self.showPrompt(self.prompt)
530 self.changeLine(completed or slice_)
531 return True
533 def _processLine(self):
535 Process current command line
537 self.history_pos = 0
538 self.execute()
539 rv = self.cout.getvalue()
540 if rv: rv = rv.strip('\n')
541 self.showReturned(rv)
542 self.cout.truncate(0)