766 lines
25 KiB
Python
766 lines
25 KiB
Python
#
|
|
# 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 gi
|
|
gi.require_version('Gtk', '3.0')
|
|
from gi.repository import Gtk
|
|
gi.require_version('Gdk', '3.0')
|
|
from gi.repository import Gdk
|
|
from gi.repository import GObject
|
|
from gi.repository import Pango
|
|
import code
|
|
import sys
|
|
import keyword
|
|
import re
|
|
|
|
def pango_pixels(value):
|
|
# The PANGO_PIXELS macro is not accessible through GObject
|
|
# Introspection. Just reimplement it:
|
|
# #define PANGO_PIXELS(d) (((int)(d) + 512) >> 10)
|
|
return (value + 512) >> 10
|
|
|
|
# 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)
|
|
def flush(self):
|
|
# The output is just a GtkTextBuffer inside a GtkTexView so I
|
|
# believe it should be always in-sync without needing to be flushed.
|
|
# Nevertheless someone might be just copy-pasting plug-in code to
|
|
# test it, for instance. So let's just add a no-op flush() method to
|
|
# get a similar interface as the real sys.stdout which we overrode.
|
|
# It avoids useless and unexpected code failure.
|
|
pass
|
|
|
|
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.ptr in self.edited:
|
|
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.WrapMode.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, False, 0.0, 0.0)
|
|
|
|
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, False, 0.0, 0.0)
|
|
|
|
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):
|
|
if not self.in_raw_input:
|
|
return Gtk.TextView.do_key_press_event(self, event)
|
|
|
|
tab_pressed = self.tab_pressed
|
|
self.tab_pressed = 0
|
|
handled = True
|
|
|
|
state = event.state & (Gdk.ModifierType.SHIFT_MASK |
|
|
Gdk.ModifierType.CONTROL_MASK |
|
|
Gdk.ModifierType.MOD1_MASK)
|
|
keyval = event.keyval
|
|
|
|
if not state:
|
|
if keyval == Gdk.KEY_Escape:
|
|
pass
|
|
elif keyval == Gdk.KEY_Return:
|
|
self._commit()
|
|
elif keyval == Gdk.KEY_Up:
|
|
self.__history(-1)
|
|
elif keyval == Gdk.KEY_Down:
|
|
self.__history(1)
|
|
elif keyval == Gdk.KEY_Left:
|
|
self.__move_cursor(-1)
|
|
elif keyval == Gdk.KEY_Right:
|
|
self.__move_cursor(1)
|
|
elif keyval == Gdk.KEY_Home:
|
|
self.__move_cursor(-10000)
|
|
elif keyval == Gdk.KEY_End:
|
|
self.__move_cursor(10000)
|
|
elif keyval == Gdk.KEY_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.ModifierType.CONTROL_MASK:
|
|
if keyval == Gdk.KEY_u:
|
|
start = self.__get_start()
|
|
end = self.__get_cursor()
|
|
self.__delete(start, end)
|
|
elif keyval == Gdk.KEY_d:
|
|
if self.quit_func:
|
|
self.quit_func()
|
|
else:
|
|
handled = False
|
|
else:
|
|
handled = False
|
|
|
|
# Handle ordinary keys
|
|
if not handled:
|
|
return Gtk.TextView.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, False, 0.0, 0.0)
|
|
|
|
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.get_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()
|
|
allocation = Gtk.Widget.get_allocation(self)
|
|
return 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(int(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 = int(col_width - len(completions[int(ind)]))
|
|
self.__insert(iter, completions[int(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, False, 0.0, 0.0)
|
|
|
|
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 builtins", self.locals)
|
|
#self.locals['builtins'].__dict__['raw_input'] = lambda text='': self.modal_raw_input(text)
|
|
self.locals['builtins'].__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 += 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__", 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)
|
|
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,)),
|
|
}
|
|
|
|
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)
|
|
|
|
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.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS)
|
|
window.add(swin)
|
|
console = Console(banner="Hello there!",
|
|
use_rlcompleter=False,
|
|
start_script="gi.require_version('Pika', '3.0')\nfrom gi.repository import Pika\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] != '-pika':
|
|
_make_window()
|