4
# Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
11
# See COPYING file that comes with this distribution.
14
# This module 'runs' python interpreter in a TextView widget.
15
# The main class is Console, usage is:
16
# Console(locals=None, banner=None, completer=None, use_rlcompleter=True, start_script='') -
17
# it creates the widget and 'starts' interactive session; see the end of
18
# this file. If start_script is not empty, it pastes it as it was entered from keyboard.
20
# This widget is not a replacement for real terminal with python running
21
# inside: GtkTextView is not a terminal.
22
# The use case is: you have a python program, you create this widget,
23
# and inspect your program interiors.
29
import gtk.keysyms as keys
35
# commonprefix() from posixpath
37
"Given a list of pathnames, returns the longest common leading component"
41
for i in range(len(prefix)):
42
if prefix[:i+1] != item[:i+1]:
49
class _ReadLine(object):
51
class History(object):
58
def commit(self, text):
59
if text and self.items[-1] != text:
60
self.items.append(text)
64
def get(self, dir, text):
65
if len(self.items) == 1:
68
if text != self.items[self.ptr]:
69
self.edited[self.ptr] = text
70
elif self.edited.has_key(self.ptr):
71
del self.edited[self.ptr]
73
self.ptr = self.ptr + dir
74
if self.ptr >= len(self.items):
77
self.ptr = len(self.items) - 1
80
return self.edited[self.ptr]
82
return self.items[self.ptr]
87
self.set_wrap_mode(gtk.WRAP_CHAR)
88
self.modify_font(pango.FontDescription("Monospace"))
90
self.buffer = self.get_buffer()
91
self.buffer.connect("insert-text", self.on_buf_insert)
92
self.buffer.connect("delete-range", self.on_buf_delete)
93
self.buffer.connect("mark-set", self.on_buf_mark_set)
94
self.do_insert = False
95
self.do_delete = False
97
self.cursor = self.buffer.create_mark("cursor",
98
self.buffer.get_start_iter(),
100
insert = self.buffer.get_insert()
101
self.cursor.set_visible(True)
102
insert.set_visible(False)
105
self.in_raw_input = False
106
self.run_on_raw_input = None
108
self.history = _ReadLine.History()
109
self.nonword_re = re.compile("[^\w\._]")
111
def freeze_undo(self):
112
try: self.begin_not_undoable_action()
116
try: self.end_not_undoable_action()
119
def raw_input(self, ps=None):
125
iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
129
self.buffer.insert(iter, self.ps)
132
self.__move_cursor_to(iter)
133
self.scroll_to_mark(self.cursor, 0.2)
135
self.in_raw_input = True
137
if self.run_on_raw_input:
138
run_now = self.run_on_raw_input
139
self.run_on_raw_input = None
140
self.buffer.insert_at_cursor(run_now + '\n')
142
def on_buf_mark_set(self, buffer, iter, mark):
143
if not mark is buffer.get_insert():
145
start = self.__get_start()
146
end = self.__get_end()
147
if iter.compare(self.__get_start()) >= 0 and \
148
iter.compare(self.__get_end()) <= 0:
149
buffer.move_mark_by_name("cursor", iter)
150
self.scroll_to_mark(self.cursor, 0.2)
152
def __insert(self, iter, text):
153
self.do_insert = True
154
self.buffer.insert(iter, text)
155
self.do_insert = False
157
def on_buf_insert(self, buf, iter, text, len):
158
if not self.in_raw_input or self.do_insert or not len:
160
buf.stop_emission("insert-text")
161
lines = text.splitlines()
166
iter = self.__get_cursor()
168
cursor = self.__get_cursor()
169
if iter.compare(self.__get_start()) < 0:
171
elif iter.compare(self.__get_end()) > 0:
174
self.__move_cursor_to(iter)
176
self.__insert(iter, l)
177
self.__move_cursor(0)
179
def __delete(self, start, end):
180
self.do_delete = True
181
self.buffer.delete(start, end)
182
self.do_delete = False
184
def on_buf_delete(self, buf, start, end):
185
if not self.in_raw_input or self.do_delete:
188
buf.stop_emission("delete-range")
191
line_start = self.__get_start()
192
line_end = self.__get_end()
194
if start.compare(line_end) > 0:
196
if end.compare(line_start) < 0:
199
self.__move_cursor(0)
201
if start.compare(line_start) < 0:
203
if end.compare(line_end) > 0:
205
self.__delete(start, end)
207
def do_key_press_event(self, event, parent_type):
208
if not self.in_raw_input:
209
return parent_type.do_key_press_event(self, event)
211
tab_pressed = self.tab_pressed
215
state = event.state & (gdk.SHIFT_MASK |
218
keyval = event.keyval
221
if keyval == keys.Return:
223
elif keyval == keys.Up:
225
elif keyval == keys.Down:
227
elif keyval == keys.Left:
228
self.__move_cursor(-1)
229
elif keyval == keys.Right:
230
self.__move_cursor(1)
231
elif keyval == keys.Home:
232
self.__move_cursor(-10000)
233
elif keyval == keys.End:
234
self.__move_cursor(10000)
235
elif keyval == keys.Tab:
236
self.tab_pressed = tab_pressed + 1
240
elif state == gdk.CONTROL_MASK:
242
start = self.__get_start()
243
end = self.__get_cursor()
244
self.__delete(start, end)
251
return parent_type.do_key_press_event(self, event)
255
def __history(self, dir):
256
text = self.__get_line()
257
new_text = self.history.get(dir, text)
258
if not new_text is None:
259
self.__replace_line(new_text)
260
self.__move_cursor(0)
261
self.scroll_to_mark(self.cursor, 0.2)
263
def __get_cursor(self):
264
return self.buffer.get_iter_at_mark(self.cursor)
265
def __get_start(self):
266
iter = self.__get_cursor()
267
iter.set_line_offset(len(self.ps))
270
iter = self.__get_cursor()
271
if not iter.ends_line():
272
iter.forward_to_line_end()
275
def __get_text(self, start, end):
276
return self.buffer.get_text(start, end, False)
278
def __move_cursor_to(self, iter):
279
self.buffer.place_cursor(iter)
280
self.buffer.move_mark_by_name("cursor", iter)
282
def __move_cursor(self, howmany):
283
iter = self.__get_cursor()
284
end = self.__get_cursor()
285
if not end.ends_line():
286
end.forward_to_line_end()
287
line_len = end.get_line_offset()
288
move_to = iter.get_line_offset() + howmany
289
move_to = min(max(move_to, len(self.ps)), line_len)
290
iter.set_line_offset(move_to)
291
self.__move_cursor_to(iter)
293
def __delete_at_cursor(self, howmany):
294
iter = self.__get_cursor()
295
end = self.__get_cursor()
296
if not end.ends_line():
297
end.forward_to_line_end()
298
line_len = end.get_line_offset()
299
erase_to = iter.get_line_offset() + howmany
300
if erase_to > line_len:
302
elif erase_to < len(self.ps):
303
erase_to = len(self.ps)
304
end.set_line_offset(erase_to)
305
self.__delete(iter, end)
307
def __get_width(self):
308
if not (self.flags() & gtk.REALIZED):
310
layout = pango.Layout(self.get_pango_context())
311
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
312
layout.set_text(letters)
313
pix_width = layout.get_pixel_size()[0]
314
return self.allocation.width * len(letters) / pix_width
316
def __print_completions(self, completions):
317
line_start = self.__get_text(self.__get_start(), self.__get_cursor())
318
line_end = self.__get_text(self.__get_cursor(), self.__get_end())
319
iter = self.buffer.get_end_iter()
320
self.__move_cursor_to(iter)
321
self.__insert(iter, "\n")
323
width = max(self.__get_width(), 4)
324
max_width = max([len(s) for s in completions])
325
n_columns = max(int(width / (max_width + 1)), 1)
326
col_width = int(width / n_columns)
327
total = len(completions)
328
col_length = total / n_columns
329
if total % n_columns:
330
col_length = col_length + 1
331
col_length = max(col_length, 1)
335
col_width = width / total
337
for i in range(col_length):
338
for j in range(n_columns):
339
ind = i + j*col_length
341
if j == n_columns - 1:
344
n_spaces = col_width - len(completions[ind])
345
self.__insert(iter, completions[ind] + " " * n_spaces)
346
self.__insert(iter, "\n")
348
self.__insert(iter, "%s%s%s" % (self.ps, line_start, line_end))
349
iter.set_line_offset(len(self.ps) + len(line_start))
350
self.__move_cursor_to(iter)
351
self.scroll_to_mark(self.cursor, 0.2)
353
def __complete(self):
354
text = self.__get_text(self.__get_start(), self.__get_cursor())
357
nonwords = self.nonword_re.findall(text)
359
last = text.rfind(nonwords[-1]) + len(nonwords[-1])
363
completions = self.complete(word)
366
prefix = commonprefix(completions)
368
start_iter = self.__get_start()
369
start_iter.forward_chars(len(start))
370
end_iter = start_iter.copy()
371
end_iter.forward_chars(len(word))
372
self.__delete(start_iter, end_iter)
373
self.__insert(end_iter, prefix)
374
elif self.tab_pressed > 1:
376
self.__print_completions(completions)
380
def complete(self, text):
383
def __get_line(self):
384
start = self.__get_start()
385
end = self.__get_end()
386
return self.buffer.get_text(start, end, False)
388
def __replace_line(self, new_text):
389
start = self.__get_start()
390
end = self.__get_end()
391
self.__delete(start, end)
392
self.__insert(end, new_text)
395
end = self.__get_cursor()
396
if not end.ends_line():
397
end.forward_to_line_end()
398
text = self.__get_line()
399
self.__move_cursor_to(end)
401
self.__insert(end, "\n")
402
self.in_raw_input = False
403
self.history.commit(text)
404
self.do_raw_input(text)
407
def do_raw_input(self, text):
410
def write(self,whatever):
411
self.buffer.insert_at_cursor(whatever)
414
class _Console(_ReadLine, code.InteractiveInterpreter):
415
def __init__(self, locals=None, banner=None,
416
completer=None, use_rlcompleter=True,
418
_ReadLine.__init__(self)
420
code.InteractiveInterpreter.__init__(self, locals)
421
self.locals['__console__'] = self
423
self.start_script = start_script
424
self.completer = completer
427
if not self.completer and use_rlcompleter:
430
self.completer = rlcompleter.Completer()
437
self.run_on_raw_input = start_script
438
self.raw_input(self.ps1)
445
self.buffer.set_text("")
448
iter = self.buffer.get_start_iter()
449
self.buffer.insert(iter, self.banner)
450
if not iter.starts_line():
451
self.buffer.insert(iter, "\n")
453
def clear(self, start_script=None):
454
if start_script is None:
455
start_script = self.start_script
457
self.start_script = start_script
460
self.run_on_raw_input = start_script
462
def do_raw_input(self, text):
464
cmd = self.cmd_buffer + "\n" + text
467
if self.runsource(cmd):
468
self.cmd_buffer = cmd
475
def runcode(self, code):
479
eval(code, self.locals)
486
def complete_attr(self, start, end):
488
obj = eval(start, self.locals)
494
if s.startswith(end):
495
completions[s] = None
496
completions = completions.keys()
498
completions = strings
501
return [start + "." + s for s in completions]
505
def complete(self, text):
511
s = self.completer.complete(text, i)
513
completions.append(s)
521
dot = text.rfind(".")
523
return self.complete_attr(text[:dot], text[dot+1:])
526
strings = keyword.kwlist
529
strings.extend(self.locals.keys())
531
try: strings.extend(eval("globals()", self.locals).keys())
535
exec "import __builtin__" in self.locals
536
strings.extend(eval("dir(__builtin__)", self.locals))
541
if s.startswith(text):
542
completions[s] = None
543
completions = completions.keys()
548
def ReadLineType(t=gtk.TextView):
549
class readline(t, _ReadLine):
550
def __init__(self, *args, **kwargs):
552
_ReadLine.__init__(self, *args, **kwargs)
553
def do_key_press_event(self, event):
554
return _ReadLine.do_key_press_event(self, event, t)
555
gobject.type_register(readline)
558
def ConsoleType(t=gtk.TextView):
559
class console(t, _Console):
560
def __init__(self, *args, **kwargs):
562
_Console.__init__(self, *args, **kwargs)
563
def do_key_press_event(self, event):
564
return _Console.do_key_press_event(self, event, t)
565
gobject.type_register(console)
568
ReadLine = ReadLineType()
569
Console = ConsoleType()
571
if __name__ == '__main__':
572
window = gtk.Window()
573
swin = gtk.ScrolledWindow()
574
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
576
swin.add(Console(banner="Hello there!",
577
use_rlcompleter=False,
578
start_script="import gtk\n" + \
579
"win = gtk.Window()\n" + \
580
"label = gtk.Label('Hello there!')\n" + \
581
"win.add(label)\n" + \
583
window.set_default_size(400, 300)
586
if not gtk.main_level():
587
window.connect("destroy", gtk.main_quit)
590
# kate: space-indent on; indent-width 4; strip on;