summaryrefslogtreecommitdiffstats
path: root/plug-ins/pygimp/plug-ins/pyconsole.py
diff options
context:
space:
mode:
Diffstat (limited to 'plug-ins/pygimp/plug-ins/pyconsole.py')
-rw-r--r--plug-ins/pygimp/plug-ins/pyconsole.py749
1 files changed, 749 insertions, 0 deletions
diff --git a/plug-ins/pygimp/plug-ins/pyconsole.py b/plug-ins/pygimp/plug-ins/pyconsole.py
new file mode 100644
index 0000000..ee096d5
--- /dev/null
+++ b/plug-ins/pygimp/plug-ins/pyconsole.py
@@ -0,0 +1,749 @@
+#
+# pyconsole.py
+#
+# Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
+# Portions of code by Geoffrey French.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public version 2.1 as
+# published by the Free Software Foundation.
+#
+# See COPYING.lib file that comes with this distribution for full text
+# of the license.
+#
+
+# This module 'runs' python interpreter in a TextView widget.
+# The main class is Console, usage is:
+# Console(locals=None, banner=None, completer=None, use_rlcompleter=True, start_script='') -
+# it creates the widget and 'starts' interactive session; see the end
+# of this file. If start_script is not empty, it pastes it as it was
+# entered from keyboard.
+#
+# Console has "command" signal which is emitted when code is about to
+# be executed. You may connect to it using console.connect or
+# console.connect_after to get your callback ran before or after the
+# code is executed.
+#
+# To modify output appearance, set attributes of console.stdout_tag and
+# console.stderr_tag.
+#
+# Console may subclass a type other than gtk.TextView, to allow syntax
+# highlighting and stuff,
+# e.g.:
+# console_type = pyconsole.ConsoleType(moo.edit.TextView)
+# console = console_type(use_rlcompleter=False, start_script="import moo\nimport gtk\n")
+#
+# This widget is not a replacement for real terminal with python running
+# inside: GtkTextView is not a terminal.
+# The use case is: you have a python program, you create this widget,
+# and inspect your program interiors.
+
+import gtk
+import gtk.gdk as gdk
+import gobject
+import pango
+import gtk.keysyms as _keys
+import code
+import sys
+import keyword
+import re
+
+# commonprefix() from posixpath
+def _commonprefix(m):
+ "Given a list of pathnames, returns the longest common leading component"
+ if not m: return ''
+ prefix = m[0]
+ for item in m:
+ for i in range(len(prefix)):
+ if prefix[:i+1] != item[:i+1]:
+ prefix = prefix[:i]
+ if i == 0:
+ return ''
+ break
+ return prefix
+
+class _ReadLine(object):
+
+ class Output(object):
+ def __init__(self, console, tag_name):
+ object.__init__(self)
+ self.buffer = console.get_buffer()
+ self.tag_name = tag_name
+ def write(self, text):
+ pos = self.buffer.get_iter_at_mark(self.buffer.get_insert())
+ self.buffer.insert_with_tags_by_name(pos, text, self.tag_name)
+
+ class History(object):
+ def __init__(self):
+ object.__init__(self)
+ self.items = ['']
+ self.ptr = 0
+ self.edited = {}
+
+ def commit(self, text):
+ if text and self.items[-1] != text:
+ self.items.append(text)
+ self.ptr = 0
+ self.edited = {}
+
+ def get(self, dir, text):
+ if len(self.items) == 1:
+ return None
+
+ if text != self.items[self.ptr]:
+ self.edited[self.ptr] = text
+ elif self.edited.has_key(self.ptr):
+ del self.edited[self.ptr]
+
+ self.ptr = self.ptr + dir
+ if self.ptr >= len(self.items):
+ self.ptr = 0
+ elif self.ptr < 0:
+ self.ptr = len(self.items) - 1
+
+ try:
+ return self.edited[self.ptr]
+ except KeyError:
+ return self.items[self.ptr]
+
+ def __init__(self, quit_func=None):
+ object.__init__(self)
+
+ self.quit_func = quit_func
+
+ self.set_wrap_mode(gtk.WRAP_CHAR)
+ self.modify_font(pango.FontDescription("Monospace"))
+
+ self.buffer = self.get_buffer()
+ self.buffer.connect("insert-text", self.on_buf_insert)
+ self.buffer.connect("delete-range", self.on_buf_delete)
+ self.buffer.connect("mark-set", self.on_buf_mark_set)
+ self.do_insert = False
+ self.do_delete = False
+
+ self.stdout_tag = self.buffer.create_tag("stdout", foreground="#006000")
+ self.stderr_tag = self.buffer.create_tag("stderr", foreground="#B00000")
+ self._stdout = _ReadLine.Output(self, "stdout")
+ self._stderr = _ReadLine.Output(self, "stderr")
+
+ self.cursor = self.buffer.create_mark("cursor",
+ self.buffer.get_start_iter(),
+ False)
+ insert = self.buffer.get_insert()
+ self.cursor.set_visible(True)
+ insert.set_visible(False)
+
+ self.ps = ''
+ self.in_raw_input = False
+ self.in_modal_raw_input = False
+ self.run_on_raw_input = None
+ self.tab_pressed = 0
+ self.history = _ReadLine.History()
+ self.nonword_re = re.compile("[^\w\._]")
+
+ def freeze_undo(self):
+ try: self.begin_not_undoable_action()
+ except: pass
+
+ def thaw_undo(self):
+ try: self.end_not_undoable_action()
+ except: pass
+
+ def raw_input(self, ps=None):
+ '''Show prompt 'ps' and enter input mode until the current input
+ is committed.'''
+
+ if ps:
+ self.ps = ps
+ else:
+ self.ps = ''
+
+ iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
+
+ if ps:
+ self.freeze_undo()
+ self.buffer.insert(iter, self.ps)
+ self.thaw_undo()
+
+ self.__move_cursor_to(iter)
+ self.scroll_to_mark(self.cursor, 0.2)
+
+ self.in_raw_input = True
+
+ if self.run_on_raw_input:
+ run_now = self.run_on_raw_input
+ self.run_on_raw_input = None
+ self.buffer.insert_at_cursor(run_now + '\n')
+
+ def modal_raw_input(self, text):
+ '''Starts raw input in modal mode. The event loop is spinned until
+ the input is committed. Returns the text entered after the prompt.'''
+ orig_ps = self.ps
+
+ self.raw_input(text)
+ self.in_modal_raw_input = True
+
+ while self.in_modal_raw_input:
+ gtk.main_iteration()
+
+ self.ps = orig_ps
+ self.in_modal_raw_input = False
+ self.in_raw_input = False
+
+ return self.modal_raw_input_result
+
+ def modal_input(self, text):
+ return eval(self.modal_raw_input(text))
+
+ # Each time the insert mark is modified, move the cursor to it.
+ def on_buf_mark_set(self, buffer, iter, mark):
+ if mark is not buffer.get_insert():
+ return
+ start = self.__get_start()
+ end = self.__get_end()
+ if iter.compare(self.__get_start()) >= 0 and \
+ iter.compare(self.__get_end()) <= 0:
+ buffer.move_mark_by_name("cursor", iter)
+ self.scroll_to_mark(self.cursor, 0.2)
+
+ def __insert(self, iter, text):
+ self.do_insert = True
+ self.buffer.insert(iter, text)
+ self.do_insert = False
+
+ # Make sure that text insertions while in text input mode are properly
+ # committed to the history.
+ def on_buf_insert(self, buf, iter, text, len):
+ # Bail out if not in input mode.
+ if not self.in_raw_input or self.do_insert or not len:
+ return
+
+ buf.stop_emission("insert-text")
+ lines = text.splitlines()
+ need_eol = False
+ for l in lines:
+ if need_eol:
+ self._commit()
+ iter = self.__get_cursor()
+ else:
+ cursor = self.__get_cursor()
+ if iter.compare(self.__get_start()) < 0:
+ iter = cursor
+ elif iter.compare(self.__get_end()) > 0:
+ iter = cursor
+ else:
+ self.__move_cursor_to(iter)
+ need_eol = True
+ self.__insert(iter, l)
+ self.__move_cursor(0)
+
+ def __delete(self, start, end):
+ self.do_delete = True
+ self.buffer.delete(start, end)
+ self.do_delete = False
+
+ def on_buf_delete(self, buf, start, end):
+ if not self.in_raw_input or self.do_delete:
+ return
+
+ buf.stop_emission("delete-range")
+
+ start.order(end)
+ line_start = self.__get_start()
+ line_end = self.__get_end()
+
+ if start.compare(line_end) > 0:
+ return
+ if end.compare(line_start) < 0:
+ return
+
+ self.__move_cursor(0)
+
+ if start.compare(line_start) < 0:
+ start = line_start
+ if end.compare(line_end) > 0:
+ end = line_end
+ self.__delete(start, end)
+
+ # We overload the key press event handler to handle "special keys"
+ # when in input mode to make history browsing, completions, etc. work.
+ def do_key_press_event(self, event, parent_type):
+ if not self.in_raw_input:
+ return parent_type.do_key_press_event(self, event)
+
+ tab_pressed = self.tab_pressed
+ self.tab_pressed = 0
+ handled = True
+
+ state = event.state & (gdk.SHIFT_MASK |
+ gdk.CONTROL_MASK |
+ gdk.MOD1_MASK)
+ keyval = event.keyval
+
+ if not state:
+ if keyval == _keys.Escape:
+ pass
+ elif keyval == _keys.Return:
+ self._commit()
+ elif keyval == _keys.Up:
+ self.__history(-1)
+ elif keyval == _keys.Down:
+ self.__history(1)
+ elif keyval == _keys.Left:
+ self.__move_cursor(-1)
+ elif keyval == _keys.Right:
+ self.__move_cursor(1)
+ elif keyval == _keys.Home:
+ self.__move_cursor(-10000)
+ elif keyval == _keys.End:
+ self.__move_cursor(10000)
+ elif keyval == _keys.Tab:
+ cursor = self.__get_cursor()
+ if cursor.starts_line():
+ handled = False
+ else:
+ cursor.backward_char()
+ if cursor.get_char().isspace():
+ handled = False
+ else:
+ self.tab_pressed = tab_pressed + 1
+ self.__complete()
+ else:
+ handled = False
+ elif state == gdk.CONTROL_MASK:
+ if keyval == _keys.u:
+ start = self.__get_start()
+ end = self.__get_cursor()
+ self.__delete(start, end)
+ elif keyval == _keys.d:
+ if self.quit_func:
+ self.quit_func()
+ else:
+ handled = False
+ else:
+ handled = False
+
+ # Handle ordinary keys
+ if not handled:
+ return parent_type.do_key_press_event(self, event)
+ else:
+ return True
+
+ def __history(self, dir):
+ text = self._get_line()
+ new_text = self.history.get(dir, text)
+ if not new_text is None:
+ self.__replace_line(new_text)
+ self.__move_cursor(0)
+ self.scroll_to_mark(self.cursor, 0.2)
+
+ def __get_cursor(self):
+ '''Returns an iterator at the current cursor position.'''
+ return self.buffer.get_iter_at_mark(self.cursor)
+
+ def __get_start(self):
+ '''Returns an iterator at the start of the input on the current
+ cursor line.'''
+
+ iter = self.__get_cursor()
+ iter.set_line(iter.get_line())
+ iter.forward_chars(len(self.ps))
+ return iter
+
+ def __get_end(self):
+ '''Returns an iterator at the end of the cursor line.'''
+ iter = self.__get_cursor()
+ if not iter.ends_line():
+ iter.forward_to_line_end()
+ return iter
+
+ def __get_text(self, start, end):
+ '''Get text between 'start' and 'end' markers.'''
+ return self.buffer.get_text(start, end, False)
+
+ def __move_cursor_to(self, iter):
+ self.buffer.place_cursor(iter)
+ self.buffer.move_mark_by_name("cursor", iter)
+
+ def __move_cursor(self, howmany):
+ iter = self.__get_cursor()
+ end = self.__get_cursor()
+ if not end.ends_line():
+ end.forward_to_line_end()
+ line_len = end.get_line_offset()
+ move_to = iter.get_line_offset() + howmany
+ move_to = min(max(move_to, len(self.ps)), line_len)
+ iter.set_line_offset(move_to)
+ self.__move_cursor_to(iter)
+
+ def __delete_at_cursor(self, howmany):
+ iter = self.__get_cursor()
+ end = self.__get_cursor()
+ if not end.ends_line():
+ end.forward_to_line_end()
+ line_len = end.get_line_offset()
+ erase_to = iter.get_line_offset() + howmany
+ if erase_to > line_len:
+ erase_to = line_len
+ elif erase_to < len(self.ps):
+ erase_to = len(self.ps)
+ end.set_line_offset(erase_to)
+ self.__delete(iter, end)
+
+ def __get_width(self):
+ '''Estimate the number of characters that will fit in the area
+ currently allocated to this widget.'''
+
+ if not (self.flags() & gtk.REALIZED):
+ return 80
+
+ context = self.get_pango_context()
+ metrics = context.get_metrics(context.get_font_description(),
+ context.get_language())
+ pix_width = metrics.get_approximate_char_width()
+ return self.allocation.width * pango.SCALE / pix_width
+
+ def __print_completions(self, completions):
+ line_start = self.__get_text(self.__get_start(), self.__get_cursor())
+ line_end = self.__get_text(self.__get_cursor(), self.__get_end())
+ iter = self.buffer.get_end_iter()
+ self.__move_cursor_to(iter)
+ self.__insert(iter, "\n")
+
+ width = max(self.__get_width(), 4)
+ max_width = max([len(s) for s in completions])
+ n_columns = max(int(width / (max_width + 1)), 1)
+ col_width = int(width / n_columns)
+ total = len(completions)
+ col_length = total / n_columns
+ if total % n_columns:
+ col_length = col_length + 1
+ col_length = max(col_length, 1)
+
+ if col_length == 1:
+ n_columns = total
+ col_width = width / total
+
+ for i in range(col_length):
+ for j in range(n_columns):
+ ind = i + j*col_length
+ if ind < total:
+ if j == n_columns - 1:
+ n_spaces = 0
+ else:
+ n_spaces = col_width - len(completions[ind])
+ self.__insert(iter, completions[ind] + " " * n_spaces)
+ self.__insert(iter, "\n")
+
+ self.__insert(iter, "%s%s%s" % (self.ps, line_start, line_end))
+ iter.set_line_offset(len(self.ps) + len(line_start))
+ self.__move_cursor_to(iter)
+ self.scroll_to_mark(self.cursor, 0.2)
+
+ def __complete(self):
+ text = self.__get_text(self.__get_start(), self.__get_cursor())
+ start = ''
+ word = text
+ nonwords = self.nonword_re.findall(text)
+ if nonwords:
+ last = text.rfind(nonwords[-1]) + len(nonwords[-1])
+ start = text[:last]
+ word = text[last:]
+
+ completions = self.complete(word)
+
+ if completions:
+ prefix = _commonprefix(completions)
+ if prefix != word:
+ start_iter = self.__get_start()
+ start_iter.forward_chars(len(start))
+ end_iter = start_iter.copy()
+ end_iter.forward_chars(len(word))
+ self.__delete(start_iter, end_iter)
+ self.__insert(end_iter, prefix)
+ elif self.tab_pressed > 1:
+ self.freeze_undo()
+ self.__print_completions(completions)
+ self.thaw_undo()
+ self.tab_pressed = 0
+
+ def complete(self, text):
+ return None
+
+ def _get_line(self):
+ '''Return the current input behind the prompt.'''
+ start = self.__get_start()
+ end = self.__get_end()
+ return self.buffer.get_text(start, end, False)
+
+ def __replace_line(self, new_text):
+ '''Replace the current input with 'new_text' '''
+ start = self.__get_start()
+ end = self.__get_end()
+ self.__delete(start, end)
+ self.__insert(end, new_text)
+
+ def _commit(self):
+ '''Commit the input entered on the current line.'''
+
+ # Find iterator and end of cursor line.
+ end = self.__get_cursor()
+ if not end.ends_line():
+ end.forward_to_line_end()
+
+ # Get text at current line.
+ text = self._get_line()
+
+ # Move cursor to the end of the line, insert new line.
+ self.__move_cursor_to(end)
+ self.freeze_undo()
+ self.__insert(end, "\n")
+
+ self.history.commit(text)
+ if self.in_modal_raw_input:
+ self.in_modal_raw_input = False
+ self.modal_raw_input_result = text
+ else:
+ self.in_raw_input = False
+ self.do_raw_input(text)
+
+ self.thaw_undo()
+
+ def do_raw_input(self, text):
+ pass
+
+
+class _Console(_ReadLine, code.InteractiveInterpreter):
+ def __init__(self, locals=None, banner=None,
+ completer=None, use_rlcompleter=True,
+ start_script=None, quit_func=None):
+ _ReadLine.__init__(self, quit_func)
+
+ code.InteractiveInterpreter.__init__(self, locals)
+ self.locals["__console__"] = self
+
+ # The builtin raw_input function reads from stdin, we don't want
+ # this. Therefore, replace this function with our own modal raw
+ # input function.
+ exec "import __builtin__" in self.locals
+ self.locals['__builtin__'].__dict__['raw_input'] = lambda text='': self.modal_raw_input(text)
+ self.locals['__builtin__'].__dict__['input'] = lambda text='': self.modal_input(text)
+
+ self.start_script = start_script
+ self.completer = completer
+ self.banner = banner
+
+ if not self.completer and use_rlcompleter:
+ try:
+ import rlcompleter
+ self.completer = rlcompleter.Completer()
+ except ImportError:
+ pass
+
+ self.ps1 = ">>> "
+ self.ps2 = "... "
+ self.__start()
+ self.run_on_raw_input = start_script
+ self.raw_input(self.ps1)
+
+ def __start(self):
+ self.cmd_buffer = ""
+
+ self.freeze_undo()
+ self.thaw_undo()
+
+ self.do_delete = True
+ self.buffer.set_text("")
+ self.do_delete = False
+
+ if self.banner:
+ iter = self.buffer.get_start_iter()
+ self.buffer.insert_with_tags_by_name(iter, self.banner, "stdout")
+ if not iter.starts_line():
+ self.buffer.insert(iter, "\n")
+
+ def clear(self, start_script=None):
+ if start_script is None:
+ start_script = self.start_script
+ else:
+ self.start_script = start_script
+
+ self.__start()
+ self.run_on_raw_input = start_script
+ self.raw_input(self.ps1)
+
+ def do_raw_input(self, text):
+ if self.cmd_buffer:
+ cmd = self.cmd_buffer + "\n" + text
+ else:
+ cmd = text
+
+ saved_stdout, saved_stderr = sys.stdout, sys.stderr
+ sys.stdout, sys.stderr = self._stdout, self._stderr
+
+ if self.runsource(cmd):
+ self.cmd_buffer = cmd
+ ps = self.ps2
+ else:
+ self.cmd_buffer = ''
+ ps = self.ps1
+
+ sys.stdout, sys.stderr = saved_stdout, saved_stderr
+ self.raw_input(ps)
+
+ def do_command(self, code):
+ try:
+ eval(code, self.locals)
+ except SystemExit:
+ if self.quit_func:
+ self.quit_func()
+ else:
+ raise
+ except:
+ self.showtraceback()
+
+ def runcode(self, code):
+ if gtk.pygtk_version[1] < 8:
+ self.do_command(code)
+ else:
+ self.emit("command", code)
+
+ def complete_attr(self, start, end):
+ try:
+ obj = eval(start, self.locals)
+ strings = dir(obj)
+
+ if end:
+ completions = {}
+ for s in strings:
+ if s.startswith(end):
+ completions[s] = None
+ completions = completions.keys()
+ else:
+ completions = strings
+
+ completions.sort()
+ return [start + "." + s for s in completions]
+ except:
+ return None
+
+ def complete(self, text):
+ if self.completer:
+ completions = []
+ i = 0
+ try:
+ while 1:
+ s = self.completer.complete(text, i)
+ if s:
+ completions.append(s)
+ i = i + 1
+ else:
+ completions.sort()
+ return completions
+ except NameError:
+ return None
+
+ dot = text.rfind(".")
+ if dot >= 0:
+ return self.complete_attr(text[:dot], text[dot+1:])
+
+ completions = {}
+ strings = keyword.kwlist
+
+ if self.locals:
+ strings.extend(self.locals.keys())
+
+ try: strings.extend(eval("globals()", self.locals).keys())
+ except: pass
+
+ try:
+ exec "import __builtin__" in self.locals
+ strings.extend(eval("dir(__builtin__)", self.locals))
+ except:
+ pass
+
+ for s in strings:
+ if s.startswith(text):
+ completions[s] = None
+ completions = completions.keys()
+ completions.sort()
+ return completions
+
+
+def ReadLineType(t=gtk.TextView):
+ class readline(t, _ReadLine):
+ def __init__(self, *args, **kwargs):
+ t.__init__(self)
+ _ReadLine.__init__(self, *args, **kwargs)
+ def do_key_press_event(self, event):
+ return _ReadLine.do_key_press_event(self, event, t)
+ gobject.type_register(readline)
+ return readline
+
+def ConsoleType(t=gtk.TextView):
+ class console_type(t, _Console):
+ __gsignals__ = {
+ 'command' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)),
+ 'key-press-event' : 'override'
+ }
+
+ def __init__(self, *args, **kwargs):
+ if gtk.pygtk_version[1] < 8:
+ gobject.GObject.__init__(self)
+ else:
+ t.__init__(self)
+ _Console.__init__(self, *args, **kwargs)
+
+ def do_command(self, code):
+ return _Console.do_command(self, code)
+
+ def do_key_press_event(self, event):
+ return _Console.do_key_press_event(self, event, t)
+
+ def get_default_size(self):
+ context = self.get_pango_context()
+ metrics = context.get_metrics(context.get_font_description(),
+ context.get_language())
+ width = metrics.get_approximate_char_width()
+ height = metrics.get_ascent() + metrics.get_descent()
+
+ # Default to a 80x40 console
+ width = pango.PIXELS(int(width * 80 * 1.05))
+ height = pango.PIXELS(height * 40)
+
+ return width, height
+
+ if gtk.pygtk_version[1] < 8:
+ gobject.type_register(console_type)
+
+ return console_type
+
+ReadLine = ReadLineType()
+Console = ConsoleType()
+
+def _make_window():
+ window = gtk.Window()
+ window.set_title("pyconsole.py")
+ swin = gtk.ScrolledWindow()
+ swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
+ window.add(swin)
+ console = Console(banner="Hello there!",
+ use_rlcompleter=False,
+ start_script="from gtk import *\n")
+ swin.add(console)
+
+ width, height = console.get_default_size()
+ sb_width, sb_height = swin.get_vscrollbar().size_request()
+
+ window.set_default_size(width + sb_width, height)
+ window.show_all()
+
+ if not gtk.main_level():
+ window.connect("destroy", gtk.main_quit)
+ gtk.main()
+
+ return console
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2 or sys.argv[1] != '-gimp':
+ _make_window()