~ubuntu-branches/ubuntu/saucy/pida/saucy

« back to all changes in this revision

Viewing changes to pida/utils/pyconsole.py

  • Committer: Bazaar Package Importer
  • Author(s): Barry deFreese
  • Date: 2006-08-01 13:08:56 UTC
  • mfrom: (0.1.2 etch) (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20060801130856-v92ktopgdxc8rv7q
Tags: 0.3.1-2ubuntu1
* Re-sync with Debian
* Remove bashisms from debian/rules

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
#   pyconsole.py
 
3
#
 
4
#   Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
 
5
#
 
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.
 
10
#
 
11
#   See COPYING file that comes with this distribution.
 
12
#
 
13
 
 
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.
 
19
#
 
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.
 
24
 
 
25
import gtk
 
26
import gtk.gdk as gdk
 
27
import gobject
 
28
import pango
 
29
import gtk.keysyms as keys
 
30
import code
 
31
import sys
 
32
import keyword
 
33
import re
 
34
 
 
35
# commonprefix() from posixpath
 
36
def commonprefix(m):
 
37
    "Given a list of pathnames, returns the longest common leading component"
 
38
    if not m: return ''
 
39
    prefix = m[0]
 
40
    for item in m:
 
41
        for i in range(len(prefix)):
 
42
            if prefix[:i+1] != item[:i+1]:
 
43
                prefix = prefix[:i]
 
44
                if i == 0:
 
45
                    return ''
 
46
                break
 
47
    return prefix
 
48
 
 
49
class _ReadLine(object):
 
50
 
 
51
    class History(object):
 
52
        def __init__(self):
 
53
            object.__init__(self)
 
54
            self.items = ['']
 
55
            self.ptr = 0
 
56
            self.edited = {}
 
57
 
 
58
        def commit(self, text):
 
59
            if text and self.items[-1] != text:
 
60
                self.items.append(text)
 
61
            self.ptr = 0
 
62
            self.edited = {}
 
63
 
 
64
        def get(self, dir, text):
 
65
            if len(self.items) == 1:
 
66
                return None
 
67
 
 
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]
 
72
 
 
73
            self.ptr = self.ptr + dir
 
74
            if self.ptr >= len(self.items):
 
75
                self.ptr = 0
 
76
            elif self.ptr < 0:
 
77
                self.ptr = len(self.items) - 1
 
78
 
 
79
            try:
 
80
                return self.edited[self.ptr]
 
81
            except KeyError:
 
82
                return self.items[self.ptr]
 
83
 
 
84
    def __init__(self):
 
85
        object.__init__(self)
 
86
 
 
87
        self.set_wrap_mode(gtk.WRAP_CHAR)
 
88
        self.modify_font(pango.FontDescription("Monospace"))
 
89
 
 
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
 
96
 
 
97
        self.cursor = self.buffer.create_mark("cursor",
 
98
                                              self.buffer.get_start_iter(),
 
99
                                              False)
 
100
        insert = self.buffer.get_insert()
 
101
        self.cursor.set_visible(True)
 
102
        insert.set_visible(False)
 
103
 
 
104
        self.ps = ''
 
105
        self.in_raw_input = False
 
106
        self.run_on_raw_input = None
 
107
        self.tab_pressed = 0
 
108
        self.history = _ReadLine.History()
 
109
        self.nonword_re = re.compile("[^\w\._]")
 
110
 
 
111
    def freeze_undo(self):
 
112
        try: self.begin_not_undoable_action()
 
113
        except: pass
 
114
 
 
115
    def thaw_undo(self):
 
116
        try: self.end_not_undoable_action()
 
117
        except: pass
 
118
 
 
119
    def raw_input(self, ps=None):
 
120
        if ps:
 
121
            self.ps = ps
 
122
        else:
 
123
            self.ps = ''
 
124
 
 
125
        iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
 
126
 
 
127
        if ps:
 
128
            self.freeze_undo()
 
129
            self.buffer.insert(iter, self.ps)
 
130
            self.thaw_undo()
 
131
 
 
132
        self.__move_cursor_to(iter)
 
133
        self.scroll_to_mark(self.cursor, 0.2)
 
134
 
 
135
        self.in_raw_input = True
 
136
 
 
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')
 
141
 
 
142
    def on_buf_mark_set(self, buffer, iter, mark):
 
143
        if not mark is buffer.get_insert():
 
144
            return
 
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)
 
151
 
 
152
    def __insert(self, iter, text):
 
153
        self.do_insert = True
 
154
        self.buffer.insert(iter, text)
 
155
        self.do_insert = False
 
156
 
 
157
    def on_buf_insert(self, buf, iter, text, len):
 
158
        if not self.in_raw_input or self.do_insert or not len:
 
159
            return
 
160
        buf.stop_emission("insert-text")
 
161
        lines = text.splitlines()
 
162
        need_eol = False
 
163
        for l in lines:
 
164
            if need_eol:
 
165
                self.__commit()
 
166
                iter = self.__get_cursor()
 
167
            else:
 
168
                cursor = self.__get_cursor()
 
169
                if iter.compare(self.__get_start()) < 0:
 
170
                    iter = cursor
 
171
                elif iter.compare(self.__get_end()) > 0:
 
172
                    iter = cursor
 
173
                else:
 
174
                    self.__move_cursor_to(iter)
 
175
            need_eol = True
 
176
            self.__insert(iter, l)
 
177
        self.__move_cursor(0)
 
178
 
 
179
    def __delete(self, start, end):
 
180
        self.do_delete = True
 
181
        self.buffer.delete(start, end)
 
182
        self.do_delete = False
 
183
 
 
184
    def on_buf_delete(self, buf, start, end):
 
185
        if not self.in_raw_input or self.do_delete:
 
186
            return
 
187
 
 
188
        buf.stop_emission("delete-range")
 
189
 
 
190
        start.order(end)
 
191
        line_start = self.__get_start()
 
192
        line_end = self.__get_end()
 
193
 
 
194
        if start.compare(line_end) > 0:
 
195
            return
 
196
        if end.compare(line_start) < 0:
 
197
            return
 
198
 
 
199
        self.__move_cursor(0)
 
200
 
 
201
        if start.compare(line_start) < 0:
 
202
            start = line_start
 
203
        if end.compare(line_end) > 0:
 
204
            end = line_end
 
205
        self.__delete(start, end)
 
206
 
 
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)
 
210
 
 
211
        tab_pressed = self.tab_pressed
 
212
        self.tab_pressed = 0
 
213
        handled = True
 
214
 
 
215
        state = event.state & (gdk.SHIFT_MASK |
 
216
                                gdk.CONTROL_MASK |
 
217
                                gdk.MOD1_MASK)
 
218
        keyval = event.keyval
 
219
 
 
220
        if not state:
 
221
            if keyval == keys.Return:
 
222
                self.__commit()
 
223
            elif keyval == keys.Up:
 
224
                self.__history(-1)
 
225
            elif keyval == keys.Down:
 
226
                self.__history(1)
 
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
 
237
                self.__complete()
 
238
            else:
 
239
                handled = False
 
240
        elif state == gdk.CONTROL_MASK:
 
241
            if keyval == keys.u:
 
242
                start = self.__get_start()
 
243
                end = self.__get_cursor()
 
244
                self.__delete(start, end)
 
245
            else:
 
246
                handled = False
 
247
        else:
 
248
            handled = False
 
249
 
 
250
        if not handled:
 
251
            return parent_type.do_key_press_event(self, event)
 
252
        else:
 
253
            return True
 
254
 
 
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)
 
262
 
 
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))
 
268
        return iter
 
269
    def __get_end(self):
 
270
        iter = self.__get_cursor()
 
271
        if not iter.ends_line():
 
272
            iter.forward_to_line_end()
 
273
        return iter
 
274
 
 
275
    def __get_text(self, start, end):
 
276
        return self.buffer.get_text(start, end, False)
 
277
 
 
278
    def __move_cursor_to(self, iter):
 
279
        self.buffer.place_cursor(iter)
 
280
        self.buffer.move_mark_by_name("cursor", iter)
 
281
 
 
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)
 
292
 
 
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:
 
301
            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)
 
306
 
 
307
    def __get_width(self):
 
308
        if not (self.flags() & gtk.REALIZED):
 
309
            return 80
 
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
 
315
 
 
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")
 
322
 
 
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)
 
332
 
 
333
        if col_length == 1:
 
334
            n_columns = total
 
335
            col_width = width / total
 
336
 
 
337
        for i in range(col_length):
 
338
            for j in range(n_columns):
 
339
                ind = i + j*col_length
 
340
                if ind < total:
 
341
                    if j == n_columns - 1:
 
342
                        n_spaces = 0
 
343
                    else:
 
344
                        n_spaces = col_width - len(completions[ind])
 
345
                    self.__insert(iter, completions[ind] + " " * n_spaces)
 
346
            self.__insert(iter, "\n")
 
347
 
 
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)
 
352
 
 
353
    def __complete(self):
 
354
        text = self.__get_text(self.__get_start(), self.__get_cursor())
 
355
        start = ''
 
356
        word = text
 
357
        nonwords = self.nonword_re.findall(text)
 
358
        if nonwords:
 
359
            last = text.rfind(nonwords[-1]) + len(nonwords[-1])
 
360
            start = text[:last]
 
361
            word = text[last:]
 
362
 
 
363
        completions = self.complete(word)
 
364
 
 
365
        if completions:
 
366
            prefix = commonprefix(completions)
 
367
            if prefix != word:
 
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:
 
375
                self.freeze_undo()
 
376
                self.__print_completions(completions)
 
377
                self.thaw_undo()
 
378
                self.tab_pressed = 0
 
379
 
 
380
    def complete(self, text):
 
381
        return None
 
382
 
 
383
    def __get_line(self):
 
384
        start = self.__get_start()
 
385
        end = self.__get_end()
 
386
        return self.buffer.get_text(start, end, False)
 
387
 
 
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)
 
393
 
 
394
    def __commit(self):
 
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)
 
400
        self.freeze_undo()
 
401
        self.__insert(end, "\n")
 
402
        self.in_raw_input = False
 
403
        self.history.commit(text)
 
404
        self.do_raw_input(text)
 
405
        self.thaw_undo()
 
406
 
 
407
    def do_raw_input(self, text):
 
408
        pass
 
409
 
 
410
    def write(self,whatever):
 
411
        self.buffer.insert_at_cursor(whatever)
 
412
 
 
413
 
 
414
class _Console(_ReadLine, code.InteractiveInterpreter):
 
415
    def __init__(self, locals=None, banner=None,
 
416
                 completer=None, use_rlcompleter=True,
 
417
                 start_script=None):
 
418
        _ReadLine.__init__(self)
 
419
 
 
420
        code.InteractiveInterpreter.__init__(self, locals)
 
421
        self.locals['__console__'] = self
 
422
 
 
423
        self.start_script = start_script
 
424
        self.completer = completer
 
425
        self.banner = banner
 
426
 
 
427
        if not self.completer and use_rlcompleter:
 
428
            try:
 
429
                import rlcompleter
 
430
                self.completer = rlcompleter.Completer()
 
431
            except ImportError:
 
432
                pass
 
433
 
 
434
        self.ps1 = ">>> "
 
435
        self.ps2 = "... "
 
436
        self.__start()
 
437
        self.run_on_raw_input = start_script
 
438
        self.raw_input(self.ps1)
 
439
 
 
440
    def __start(self):
 
441
        self.cmd_buffer = ""
 
442
 
 
443
        self.freeze_undo()
 
444
        self.thaw_undo()
 
445
        self.buffer.set_text("")
 
446
 
 
447
        if self.banner:
 
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")
 
452
 
 
453
    def clear(self, start_script=None):
 
454
        if start_script is None:
 
455
            start_script = self.start_script
 
456
        else:
 
457
            self.start_script = start_script
 
458
 
 
459
        self.__start()
 
460
        self.run_on_raw_input = start_script
 
461
 
 
462
    def do_raw_input(self, text):
 
463
        if self.cmd_buffer:
 
464
            cmd = self.cmd_buffer + "\n" + text
 
465
        else:
 
466
            cmd = text
 
467
        if self.runsource(cmd):
 
468
            self.cmd_buffer = cmd
 
469
            ps = self.ps2
 
470
        else:
 
471
            self.cmd_buffer = ''
 
472
            ps = self.ps1
 
473
        self.raw_input(ps)
 
474
 
 
475
    def runcode(self, code):
 
476
        saved = sys.stdout
 
477
        sys.stdout = self
 
478
        try:
 
479
            eval(code, self.locals)
 
480
        except SystemExit:
 
481
            raise
 
482
        except:
 
483
            self.showtraceback()
 
484
        sys.stdout = saved
 
485
 
 
486
    def complete_attr(self, start, end):
 
487
        try:
 
488
            obj = eval(start, self.locals)
 
489
            strings = dir(obj)
 
490
 
 
491
            if end:
 
492
                completions = {}
 
493
                for s in strings:
 
494
                    if s.startswith(end):
 
495
                        completions[s] = None
 
496
                completions = completions.keys()
 
497
            else:
 
498
                completions = strings
 
499
 
 
500
            completions.sort()
 
501
            return [start + "." + s for s in completions]
 
502
        except:
 
503
            return None
 
504
 
 
505
    def complete(self, text):
 
506
        if self.completer:
 
507
            completions = []
 
508
            i = 0
 
509
            try:
 
510
                while 1:
 
511
                    s = self.completer.complete(text, i)
 
512
                    if s:
 
513
                        completions.append(s)
 
514
                        i = i + 1
 
515
                    else:
 
516
                        completions.sort()
 
517
                        return completions
 
518
            except NameError:
 
519
                return None
 
520
 
 
521
        dot = text.rfind(".")
 
522
        if dot >= 0:
 
523
            return self.complete_attr(text[:dot], text[dot+1:])
 
524
 
 
525
        completions = {}
 
526
        strings = keyword.kwlist
 
527
 
 
528
        if self.locals:
 
529
            strings.extend(self.locals.keys())
 
530
 
 
531
        try: strings.extend(eval("globals()", self.locals).keys())
 
532
        except: pass
 
533
 
 
534
        try:
 
535
            exec "import __builtin__" in self.locals
 
536
            strings.extend(eval("dir(__builtin__)", self.locals))
 
537
        except:
 
538
            pass
 
539
 
 
540
        for s in strings:
 
541
            if s.startswith(text):
 
542
                completions[s] = None
 
543
        completions = completions.keys()
 
544
        completions.sort()
 
545
        return completions
 
546
 
 
547
 
 
548
def ReadLineType(t=gtk.TextView):
 
549
    class readline(t, _ReadLine):
 
550
        def __init__(self, *args, **kwargs):
 
551
            t.__init__(self)
 
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)
 
556
    return readline
 
557
 
 
558
def ConsoleType(t=gtk.TextView):
 
559
    class console(t, _Console):
 
560
        def __init__(self, *args, **kwargs):
 
561
            t.__init__(self)
 
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)
 
566
    return console
 
567
 
 
568
ReadLine = ReadLineType()
 
569
Console = ConsoleType()
 
570
 
 
571
if __name__ == '__main__':
 
572
    window = gtk.Window()
 
573
    swin = gtk.ScrolledWindow()
 
574
    swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
 
575
    window.add(swin)
 
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" + \
 
582
                                  "win.show_all()\n"))
 
583
    window.set_default_size(400, 300)
 
584
    window.show_all()
 
585
 
 
586
    if not gtk.main_level():
 
587
        window.connect("destroy", gtk.main_quit)
 
588
        gtk.main()
 
589
 
 
590
# kate: space-indent on; indent-width 4; strip on;