aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRonny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>2011-03-21 17:15:34 +0100
committerRonny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>2011-03-21 17:15:34 +0100
commit97216aa12ad931c89522381164c204c90a16c294 (patch)
treef92bce444cf3de878eb71babea59affa80d8349e /lib_pypy/pyrepl
parentbranch that kills the subrepos for using dependencies instead (diff)
downloadpypy-97216aa12ad931c89522381164c204c90a16c294.tar.gz
pypy-97216aa12ad931c89522381164c204c90a16c294.tar.bz2
pypy-97216aa12ad931c89522381164c204c90a16c294.zip
inline pyrepl
Diffstat (limited to 'lib_pypy/pyrepl')
-rw-r--r--lib_pypy/pyrepl/__init__.py19
-rw-r--r--lib_pypy/pyrepl/cmdrepl.py118
-rw-r--r--lib_pypy/pyrepl/commands.py385
-rw-r--r--lib_pypy/pyrepl/completer.py87
-rw-r--r--lib_pypy/pyrepl/completing_reader.py280
-rw-r--r--lib_pypy/pyrepl/console.py93
-rw-r--r--lib_pypy/pyrepl/copy_code.py73
-rw-r--r--lib_pypy/pyrepl/curses.py39
-rw-r--r--lib_pypy/pyrepl/fancy_termios.py52
-rw-r--r--lib_pypy/pyrepl/historical_reader.py311
-rw-r--r--lib_pypy/pyrepl/input.py97
-rw-r--r--lib_pypy/pyrepl/keymap.py186
-rw-r--r--lib_pypy/pyrepl/keymaps.py140
-rw-r--r--lib_pypy/pyrepl/module_lister.py70
-rw-r--r--lib_pypy/pyrepl/pygame_console.py353
-rw-r--r--lib_pypy/pyrepl/pygame_keymap.py250
-rw-r--r--lib_pypy/pyrepl/python_reader.py392
-rw-r--r--lib_pypy/pyrepl/reader.py585
-rw-r--r--lib_pypy/pyrepl/readline.py404
-rw-r--r--lib_pypy/pyrepl/simple_interact.py64
-rw-r--r--lib_pypy/pyrepl/test/test_functional.py50
-rw-r--r--lib_pypy/pyrepl/tests/__init__.py20
-rw-r--r--lib_pypy/pyrepl/tests/basic.py115
-rw-r--r--lib_pypy/pyrepl/tests/bugs.py36
-rw-r--r--lib_pypy/pyrepl/tests/infrastructure.py82
-rw-r--r--lib_pypy/pyrepl/tests/wishes.py38
-rw-r--r--lib_pypy/pyrepl/unicodedata_.py59
-rw-r--r--lib_pypy/pyrepl/unix_console.py558
-rw-r--r--lib_pypy/pyrepl/unix_eventqueue.py86
29 files changed, 5042 insertions, 0 deletions
diff --git a/lib_pypy/pyrepl/__init__.py b/lib_pypy/pyrepl/__init__.py
new file mode 100644
index 0000000000..1693cbd0b9
--- /dev/null
+++ b/lib_pypy/pyrepl/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
+# Armin Rigo
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/lib_pypy/pyrepl/cmdrepl.py b/lib_pypy/pyrepl/cmdrepl.py
new file mode 100644
index 0000000000..7805a6a63c
--- /dev/null
+++ b/lib_pypy/pyrepl/cmdrepl.py
@@ -0,0 +1,118 @@
+# Copyright 2000-2007 Michael Hudson-Doyle <micahel@gmail.com>
+# Maciek Fijalkowski
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""Wedge pyrepl behaviour into cmd.Cmd-derived classes.
+
+replize, when given a subclass of cmd.Cmd, returns a class that
+behaves almost identically to the supplied class, except that it uses
+pyrepl instead if raw_input.
+
+It was designed to let you do this:
+
+>>> import pdb
+>>> from pyrepl import replize
+>>> pdb.Pdb = replize(pdb.Pdb)
+
+which is in fact done by the `pythoni' script that comes with
+pyrepl."""
+
+from __future__ import nested_scopes
+
+from pyrepl import completing_reader as cr, reader, completer
+from pyrepl.completing_reader import CompletingReader as CR
+import cmd
+
+class CmdReader(CR):
+ def collect_keymap(self):
+ return super(CmdReader, self).collect_keymap() + (
+ ("\\M-\\n", "invalid-key"),
+ ("\\n", "accept"))
+
+ CR_init = CR.__init__
+ def __init__(self, completions):
+ self.CR_init(self)
+ self.completions = completions
+
+ def get_completions(self, stem):
+ if len(stem) != self.pos:
+ return []
+ return cr.uniqify([s for s in self.completions
+ if s.startswith(stem)])
+
+def replize(klass, history_across_invocations=1):
+
+ """Return a subclass of the cmd.Cmd-derived klass that uses
+ pyrepl instead of readline.
+
+ Raises a ValueError if klass does not derive from cmd.Cmd.
+
+ The optional history_across_invocations parameter (default 1)
+ controls whether instances of the returned class share
+ histories."""
+
+ completions = [s[3:]
+ for s in completer.get_class_members(klass)
+ if s.startswith("do_")]
+
+ if not issubclass(klass, cmd.Cmd):
+ raise Exception
+# if klass.cmdloop.im_class is not cmd.Cmd:
+# print "this may not work"
+
+ class CmdRepl(klass):
+ k_init = klass.__init__
+
+ if history_across_invocations:
+ _CmdRepl__history = []
+ def __init__(self, *args, **kw):
+ self.k_init(*args, **kw)
+ self.__reader = CmdReader(completions)
+ self.__reader.history = CmdRepl._CmdRepl__history
+ self.__reader.historyi = len(CmdRepl._CmdRepl__history)
+ else:
+ def __init__(self, *args, **kw):
+ self.k_init(*args, **kw)
+ self.__reader = CmdReader(completions)
+
+ def cmdloop(self, intro=None):
+ self.preloop()
+ if intro is not None:
+ self.intro = intro
+ if self.intro:
+ print self.intro
+ stop = None
+ while not stop:
+ if self.cmdqueue:
+ line = self.cmdqueue[0]
+ del self.cmdqueue[0]
+ else:
+ try:
+ self.__reader.ps1 = self.prompt
+ line = self.__reader.readline()
+ except EOFError:
+ line = "EOF"
+ line = self.precmd(line)
+ stop = self.onecmd(line)
+ stop = self.postcmd(stop, line)
+ self.postloop()
+
+ CmdRepl.__name__ = "replize(%s.%s)"%(klass.__module__, klass.__name__)
+ return CmdRepl
+
diff --git a/lib_pypy/pyrepl/commands.py b/lib_pypy/pyrepl/commands.py
new file mode 100644
index 0000000000..fc6b27c31d
--- /dev/null
+++ b/lib_pypy/pyrepl/commands.py
@@ -0,0 +1,385 @@
+# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
+# Antonio Cuni
+# Armin Rigo
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import sys, os
+
+# Catgories of actions:
+# killing
+# yanking
+# motion
+# editing
+# history
+# finishing
+# [completion]
+
+class Command(object):
+ finish = 0
+ kills_digit_arg = 1
+ def __init__(self, reader, (event_name, event)):
+ self.reader = reader
+ self.event = event
+ self.event_name = event_name
+ def do(self):
+ pass
+
+class KillCommand(Command):
+ def kill_range(self, start, end):
+ if start == end:
+ return
+ r = self.reader
+ b = r.buffer
+ text = b[start:end]
+ del b[start:end]
+ if is_kill(r.last_command):
+ if start < r.pos:
+ r.kill_ring[-1] = text + r.kill_ring[-1]
+ else:
+ r.kill_ring[-1] = r.kill_ring[-1] + text
+ else:
+ r.kill_ring.append(text)
+ r.pos = start
+ r.dirty = 1
+
+class YankCommand(Command):
+ pass
+
+class MotionCommand(Command):
+ pass
+
+class EditCommand(Command):
+ pass
+
+class FinishCommand(Command):
+ finish = 1
+ pass
+
+def is_kill(command):
+ return command and issubclass(command, KillCommand)
+
+def is_yank(command):
+ return command and issubclass(command, YankCommand)
+
+# etc
+
+class digit_arg(Command):
+ kills_digit_arg = 0
+ def do(self):
+ r = self.reader
+ c = self.event[-1]
+ if c == "-":
+ if r.arg is not None:
+ r.arg = -r.arg
+ else:
+ r.arg = -1
+ else:
+ d = int(c)
+ if r.arg is None:
+ r.arg = d
+ else:
+ if r.arg < 0:
+ r.arg = 10*r.arg - d
+ else:
+ r.arg = 10*r.arg + d
+ r.dirty = 1
+
+class clear_screen(Command):
+ def do(self):
+ r = self.reader
+ r.console.clear()
+ r.dirty = 1
+
+class refresh(Command):
+ def do(self):
+ self.reader.dirty = 1
+
+class repaint(Command):
+ def do(self):
+ self.reader.dirty = 1
+ self.reader.console.repaint_prep()
+
+class kill_line(KillCommand):
+ def do(self):
+ r = self.reader
+ b = r.buffer
+ eol = r.eol()
+ for c in b[r.pos:eol]:
+ if not c.isspace():
+ self.kill_range(r.pos, eol)
+ return
+ else:
+ self.kill_range(r.pos, eol+1)
+
+class unix_line_discard(KillCommand):
+ def do(self):
+ r = self.reader
+ self.kill_range(r.bol(), r.pos)
+
+# XXX unix_word_rubout and backward_kill_word should actually
+# do different things...
+
+class unix_word_rubout(KillCommand):
+ def do(self):
+ r = self.reader
+ for i in range(r.get_arg()):
+ self.kill_range(r.bow(), r.pos)
+
+class kill_word(KillCommand):
+ def do(self):
+ r = self.reader
+ for i in range(r.get_arg()):
+ self.kill_range(r.pos, r.eow())
+
+class backward_kill_word(KillCommand):
+ def do(self):
+ r = self.reader
+ for i in range(r.get_arg()):
+ self.kill_range(r.bow(), r.pos)
+
+class yank(YankCommand):
+ def do(self):
+ r = self.reader
+ if not r.kill_ring:
+ r.error("nothing to yank")
+ return
+ r.insert(r.kill_ring[-1])
+
+class yank_pop(YankCommand):
+ def do(self):
+ r = self.reader
+ b = r.buffer
+ if not r.kill_ring:
+ r.error("nothing to yank")
+ return
+ if not is_yank(r.last_command):
+ r.error("previous command was not a yank")
+ return
+ repl = len(r.kill_ring[-1])
+ r.kill_ring.insert(0, r.kill_ring.pop())
+ t = r.kill_ring[-1]
+ b[r.pos - repl:r.pos] = t
+ r.pos = r.pos - repl + len(t)
+ r.dirty = 1
+
+class interrupt(FinishCommand):
+ def do(self):
+ import signal
+ self.reader.console.finish()
+ os.kill(os.getpid(), signal.SIGINT)
+
+class suspend(Command):
+ def do(self):
+ import signal
+ r = self.reader
+ p = r.pos
+ r.console.finish()
+ os.kill(os.getpid(), signal.SIGSTOP)
+ ## this should probably be done
+ ## in a handler for SIGCONT?
+ r.console.prepare()
+ r.pos = p
+ r.posxy = 0, 0
+ r.dirty = 1
+ r.console.screen = []
+
+class up(MotionCommand):
+ def do(self):
+ r = self.reader
+ for i in range(r.get_arg()):
+ bol1 = r.bol()
+ if bol1 == 0:
+ if r.historyi > 0:
+ r.select_item(r.historyi - 1)
+ return
+ r.pos = 0
+ r.error("start of buffer")
+ return
+ bol2 = r.bol(bol1-1)
+ line_pos = r.pos - bol1
+ if line_pos > bol1 - bol2 - 1:
+ r.sticky_y = line_pos
+ r.pos = bol1 - 1
+ else:
+ r.pos = bol2 + line_pos
+
+class down(MotionCommand):
+ def do(self):
+ r = self.reader
+ b = r.buffer
+ for i in range(r.get_arg()):
+ bol1 = r.bol()
+ eol1 = r.eol()
+ if eol1 == len(b):
+ if r.historyi < len(r.history):
+ r.select_item(r.historyi + 1)
+ r.pos = r.eol(0)
+ return
+ r.pos = len(b)
+ r.error("end of buffer")
+ return
+ eol2 = r.eol(eol1+1)
+ if r.pos - bol1 > eol2 - eol1 - 1:
+ r.pos = eol2
+ else:
+ r.pos = eol1 + (r.pos - bol1) + 1
+
+class left(MotionCommand):
+ def do(self):
+ r = self.reader
+ for i in range(r.get_arg()):
+ p = r.pos - 1
+ if p >= 0:
+ r.pos = p
+ else:
+ self.reader.error("start of buffer")
+
+class right(MotionCommand):
+ def do(self):
+ r = self.reader
+ b = r.buffer
+ for i in range(r.get_arg()):
+ p = r.pos + 1
+ if p <= len(b):
+ r.pos = p
+ else:
+ self.reader.error("end of buffer")
+
+class beginning_of_line(MotionCommand):
+ def do(self):
+ self.reader.pos = self.reader.bol()
+
+class end_of_line(MotionCommand):
+ def do(self):
+ r = self.reader
+ self.reader.pos = self.reader.eol()
+
+class home(MotionCommand):
+ def do(self):
+ self.reader.pos = 0
+
+class end(MotionCommand):
+ def do(self):
+ self.reader.pos = len(self.reader.buffer)
+
+class forward_word(MotionCommand):
+ def do(self):
+ r = self.reader
+ for i in range(r.get_arg()):
+ r.pos = r.eow()
+
+class backward_word(MotionCommand):
+ def do(self):
+ r = self.reader
+ for i in range(r.get_arg()):
+ r.pos = r.bow()
+
+class self_insert(EditCommand):
+ def do(self):
+ r = self.reader
+ r.insert(self.event * r.get_arg())
+
+class insert_nl(EditCommand):
+ def do(self):
+ r = self.reader
+ r.insert("\n" * r.get_arg())
+
+class transpose_characters(EditCommand):
+ def do(self):
+ r = self.reader
+ b = r.buffer
+ s = r.pos - 1
+ if s < 0:
+ r.error("cannot transpose at start of buffer")
+ else:
+ if s == len(b):
+ s -= 1
+ t = min(s + r.get_arg(), len(b) - 1)
+ c = b[s]
+ del b[s]
+ b.insert(t, c)
+ r.pos = t
+ r.dirty = 1
+
+class backspace(EditCommand):
+ def do(self):
+ r = self.reader
+ b = r.buffer
+ for i in range(r.get_arg()):
+ if r.pos > 0:
+ r.pos -= 1
+ del b[r.pos]
+ r.dirty = 1
+ else:
+ self.reader.error("can't backspace at start")
+
+class delete(EditCommand):
+ def do(self):
+ r = self.reader
+ b = r.buffer
+ if ( r.pos == 0 and len(b) == 0 # this is something of a hack
+ and self.event[-1] == "\004"):
+ r.update_screen()
+ r.console.finish()
+ raise EOFError
+ for i in range(r.get_arg()):
+ if r.pos != len(b):
+ del b[r.pos]
+ r.dirty = 1
+ else:
+ self.reader.error("end of buffer")
+
+class accept(FinishCommand):
+ def do(self):
+ pass
+
+class help(Command):
+ def do(self):
+ self.reader.msg = self.reader.help_text
+ self.reader.dirty = 1
+
+class invalid_key(Command):
+ def do(self):
+ pending = self.reader.console.getpending()
+ s = ''.join(self.event) + pending.data
+ self.reader.error("`%r' not bound"%s)
+
+class invalid_command(Command):
+ def do(self):
+ s = self.event_name
+ self.reader.error("command `%s' not known"%s)
+
+class qIHelp(Command):
+ def do(self):
+ r = self.reader
+ r.insert((self.event + r.console.getpending().data) * r.get_arg())
+ r.pop_input_trans()
+
+from pyrepl import input
+
+class QITrans(object):
+ def push(self, evt):
+ self.evt = evt
+ def get(self):
+ return ('qIHelp', self.evt.raw)
+
+class quoted_insert(Command):
+ kills_digit_arg = 0
+ def do(self):
+ self.reader.push_input_trans(QITrans())
diff --git a/lib_pypy/pyrepl/completer.py b/lib_pypy/pyrepl/completer.py
new file mode 100644
index 0000000000..4ce7f653d6
--- /dev/null
+++ b/lib_pypy/pyrepl/completer.py
@@ -0,0 +1,87 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import __builtin__
+
+class Completer:
+ def __init__(self, ns):
+ self.ns = ns
+
+ def complete(self, text):
+ if "." in text:
+ return self.attr_matches(text)
+ else:
+ return self.global_matches(text)
+
+ def global_matches(self, text):
+ """Compute matches when text is a simple name.
+
+ Return a list of all keywords, built-in functions and names
+ currently defines in __main__ that match.
+
+ """
+ import keyword
+ matches = []
+ n = len(text)
+ for list in [keyword.kwlist,
+ __builtin__.__dict__.keys(),
+ self.ns.keys()]:
+ for word in list:
+ if word[:n] == text and word != "__builtins__":
+ matches.append(word)
+ return matches
+
+ def attr_matches(self, text):
+ """Compute matches when text contains a dot.
+
+ Assuming the text is of the form NAME.NAME....[NAME], and is
+ evaluatable in the globals of __main__, it will be evaluated
+ and its attributes (as revealed by dir()) are used as possible
+ completions. (For class instances, class members are are also
+ considered.)
+
+ WARNING: this can still invoke arbitrary C code, if an object
+ with a __getattr__ hook is evaluated.
+
+ """
+ import re
+ m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
+ if not m:
+ return []
+ expr, attr = m.group(1, 3)
+ object = eval(expr, self.ns)
+ words = dir(object)
+ if hasattr(object, '__class__'):
+ words.append('__class__')
+ words = words + get_class_members(object.__class__)
+ matches = []
+ n = len(attr)
+ for word in words:
+ if word[:n] == attr and word != "__builtins__":
+ matches.append("%s.%s" % (expr, word))
+ return matches
+
+def get_class_members(klass):
+ ret = dir(klass)
+ if hasattr(klass, '__bases__'):
+ for base in klass.__bases__:
+ ret = ret + get_class_members(base)
+ return ret
+
+
diff --git a/lib_pypy/pyrepl/completing_reader.py b/lib_pypy/pyrepl/completing_reader.py
new file mode 100644
index 0000000000..d98753ada2
--- /dev/null
+++ b/lib_pypy/pyrepl/completing_reader.py
@@ -0,0 +1,280 @@
+# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
+# Antonio Cuni
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from pyrepl import commands, reader
+from pyrepl.reader import Reader
+
+def uniqify(l):
+ d = {}
+ for i in l:
+ d[i] = 1
+ r = d.keys()
+ r.sort()
+ return r
+
+def prefix(wordlist, j = 0):
+ d = {}
+ i = j
+ try:
+ while 1:
+ for word in wordlist:
+ d[word[i]] = 1
+ if len(d) > 1:
+ return wordlist[0][j:i]
+ i += 1
+ d = {}
+ except IndexError:
+ return wordlist[0][j:i]
+
+import re
+def stripcolor(s):
+ return stripcolor.regexp.sub('', s)
+stripcolor.regexp = re.compile(r"\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[m|K]")
+
+def real_len(s):
+ return len(stripcolor(s))
+
+def left_align(s, maxlen):
+ stripped = stripcolor(s)
+ if len(stripped) > maxlen:
+ # too bad, we remove the color
+ return stripped[:maxlen]
+ padding = maxlen - len(stripped)
+ return s + ' '*padding
+
+def build_menu(cons, wordlist, start, use_brackets, sort_in_column):
+ if use_brackets:
+ item = "[ %s ]"
+ padding = 4
+ else:
+ item = "%s "
+ padding = 2
+ maxlen = min(max(map(real_len, wordlist)), cons.width - padding)
+ cols = cons.width / (maxlen + padding)
+ rows = (len(wordlist) - 1)/cols + 1
+
+ if sort_in_column:
+ # sort_in_column=False (default) sort_in_column=True
+ # A B C A D G
+ # D E F B E
+ # G C F
+ #
+ # "fill" the table with empty words, so we always have the same amout
+ # of rows for each column
+ missing = cols*rows - len(wordlist)
+ wordlist = wordlist + ['']*missing
+ indexes = [(i%cols)*rows + i//cols for i in range(len(wordlist))]
+ wordlist = [wordlist[i] for i in indexes]
+ menu = []
+ i = start
+ for r in range(rows):
+ row = []
+ for col in range(cols):
+ row.append(item % left_align(wordlist[i], maxlen))
+ i += 1
+ if i >= len(wordlist):
+ break
+ menu.append( ''.join(row) )
+ if i >= len(wordlist):
+ i = 0
+ break
+ if r + 5 > cons.height:
+ menu.append(" %d more... "%(len(wordlist) - i))
+ break
+ return menu, i
+
+# this gets somewhat user interface-y, and as a result the logic gets
+# very convoluted.
+#
+# To summarise the summary of the summary:- people are a problem.
+# -- The Hitch-Hikers Guide to the Galaxy, Episode 12
+
+#### Desired behaviour of the completions commands.
+# the considerations are:
+# (1) how many completions are possible
+# (2) whether the last command was a completion
+# (3) if we can assume that the completer is going to return the same set of
+# completions: this is controlled by the ``assume_immutable_completions``
+# variable on the reader, which is True by default to match the historical
+# behaviour of pyrepl, but e.g. False in the ReadlineAlikeReader to match
+# more closely readline's semantics (this is needed e.g. by
+# fancycompleter)
+#
+# if there's no possible completion, beep at the user and point this out.
+# this is easy.
+#
+# if there's only one possible completion, stick it in. if the last thing
+# user did was a completion, point out that he isn't getting anywhere, but
+# only if the ``assume_immutable_completions`` is True.
+#
+# now it gets complicated.
+#
+# for the first press of a completion key:
+# if there's a common prefix, stick it in.
+
+# irrespective of whether anything got stuck in, if the word is now
+# complete, show the "complete but not unique" message
+
+# if there's no common prefix and if the word is not now complete,
+# beep.
+
+# common prefix -> yes no
+# word complete \/
+# yes "cbnu" "cbnu"
+# no - beep
+
+# for the second bang on the completion key
+# there will necessarily be no common prefix
+# show a menu of the choices.
+
+# for subsequent bangs, rotate the menu around (if there are sufficient
+# choices).
+
+class complete(commands.Command):
+ def do(self):
+ r = self.reader
+ stem = r.get_stem()
+ if r.assume_immutable_completions and \
+ r.last_command_is(self.__class__):
+ completions = r.cmpltn_menu_choices
+ else:
+ r.cmpltn_menu_choices = completions = \
+ r.get_completions(stem)
+ if len(completions) == 0:
+ r.error("no matches")
+ elif len(completions) == 1:
+ if r.assume_immutable_completions and \
+ len(completions[0]) == len(stem) and \
+ r.last_command_is(self.__class__):
+ r.msg = "[ sole completion ]"
+ r.dirty = 1
+ r.insert(completions[0][len(stem):])
+ else:
+ p = prefix(completions, len(stem))
+ if p <> '':
+ r.insert(p)
+ if r.last_command_is(self.__class__):
+ if not r.cmpltn_menu_vis:
+ r.cmpltn_menu_vis = 1
+ r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
+ r.console, completions, r.cmpltn_menu_end,
+ r.use_brackets, r.sort_in_column)
+ r.dirty = 1
+ elif stem + p in completions:
+ r.msg = "[ complete but not unique ]"
+ r.dirty = 1
+ else:
+ r.msg = "[ not unique ]"
+ r.dirty = 1
+
+class self_insert(commands.self_insert):
+ def do(self):
+ commands.self_insert.do(self)
+ r = self.reader
+ if r.cmpltn_menu_vis:
+ stem = r.get_stem()
+ if len(stem) < 1:
+ r.cmpltn_reset()
+ else:
+ completions = [w for w in r.cmpltn_menu_choices
+ if w.startswith(stem)]
+ if completions:
+ r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
+ r.console, completions, 0,
+ r.use_brackets, r.sort_in_column)
+ else:
+ r.cmpltn_reset()
+
+class CompletingReader(Reader):
+ """Adds completion support
+
+ Adds instance variables:
+ * cmpltn_menu, cmpltn_menu_vis, cmpltn_menu_end, cmpltn_choices:
+ *
+ """
+ # see the comment for the complete command
+ assume_immutable_completions = True
+ use_brackets = True # display completions inside []
+ sort_in_column = False
+
+ def collect_keymap(self):
+ return super(CompletingReader, self).collect_keymap() + (
+ (r'\t', 'complete'),)
+
+ def __init__(self, console):
+ super(CompletingReader, self).__init__(console)
+ self.cmpltn_menu = ["[ menu 1 ]", "[ menu 2 ]"]
+ self.cmpltn_menu_vis = 0
+ self.cmpltn_menu_end = 0
+ for c in [complete, self_insert]:
+ self.commands[c.__name__] = c
+ self.commands[c.__name__.replace('_', '-')] = c
+
+ def after_command(self, cmd):
+ super(CompletingReader, self).after_command(cmd)
+ if not isinstance(cmd, complete) and not isinstance(cmd, self_insert):
+ self.cmpltn_reset()
+
+ def calc_screen(self):
+ screen = super(CompletingReader, self).calc_screen()
+ if self.cmpltn_menu_vis:
+ ly = self.lxy[1]
+ screen[ly:ly] = self.cmpltn_menu
+ self.screeninfo[ly:ly] = [(0, [])]*len(self.cmpltn_menu)
+ self.cxy = self.cxy[0], self.cxy[1] + len(self.cmpltn_menu)
+ return screen
+
+ def finish(self):
+ super(CompletingReader, self).finish()
+ self.cmpltn_reset()
+
+ def cmpltn_reset(self):
+ self.cmpltn_menu = []
+ self.cmpltn_menu_vis = 0
+ self.cmpltn_menu_end = 0
+ self.cmpltn_menu_choices = []
+
+ def get_stem(self):
+ st = self.syntax_table
+ SW = reader.SYNTAX_WORD
+ b = self.buffer
+ p = self.pos - 1
+ while p >= 0 and st.get(b[p], SW) == SW:
+ p -= 1
+ return u''.join(b[p+1:self.pos])
+
+ def get_completions(self, stem):
+ return []
+
+def test():
+ class TestReader(CompletingReader):
+ def get_completions(self, stem):
+ return [s for l in map(lambda x:x.split(),self.history)
+ for s in l if s and s.startswith(stem)]
+ reader = TestReader()
+ reader.ps1 = "c**> "
+ reader.ps2 = "c/*> "
+ reader.ps3 = "c|*> "
+ reader.ps4 = "c\*> "
+ while reader.readline():
+ pass
+
+if __name__=='__main__':
+ test()
diff --git a/lib_pypy/pyrepl/console.py b/lib_pypy/pyrepl/console.py
new file mode 100644
index 0000000000..05795756db
--- /dev/null
+++ b/lib_pypy/pyrepl/console.py
@@ -0,0 +1,93 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+class Event:
+ """An Event. `evt' is 'key' or somesuch."""
+
+ def __init__(self, evt, data, raw=''):
+ self.evt = evt
+ self.data = data
+ self.raw = raw
+
+ def __repr__(self):
+ return 'Event(%r, %r)'%(self.evt, self.data)
+
+class Console:
+ """Attributes:
+
+ screen,
+ height,
+ width,
+ """
+
+ def refresh(self, screen, xy):
+ pass
+
+ def prepare(self):
+ pass
+
+ def restore(self):
+ pass
+
+ def move_cursor(self, x, y):
+ pass
+
+ def set_cursor_vis(self, vis):
+ pass
+
+ def getheightwidth(self):
+ """Return (height, width) where height and width are the height
+ and width of the terminal window in characters."""
+ pass
+
+ def get_event(self, block=1):
+ """Return an Event instance. Returns None if |block| is false
+ and there is no event pending, otherwise waits for the
+ completion of an event."""
+ pass
+
+ def beep(self):
+ pass
+
+ def clear(self):
+ """Wipe the screen"""
+ pass
+
+ def finish(self):
+ """Move the cursor to the end of the display and otherwise get
+ ready for end. XXX could be merged with restore? Hmm."""
+ pass
+
+ def flushoutput(self):
+ """Flush all output to the screen (assuming there's some
+ buffering going on somewhere)."""
+ pass
+
+ def forgetinput(self):
+ """Forget all pending, but not yet processed input."""
+ pass
+
+ def getpending(self):
+ """Return the characters that have been typed but not yet
+ processed."""
+ pass
+
+ def wait(self):
+ """Wait for an event."""
+ pass
diff --git a/lib_pypy/pyrepl/copy_code.py b/lib_pypy/pyrepl/copy_code.py
new file mode 100644
index 0000000000..0366ad70f8
--- /dev/null
+++ b/lib_pypy/pyrepl/copy_code.py
@@ -0,0 +1,73 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import new
+
+def copy_code_with_changes(codeobject,
+ argcount=None,
+ nlocals=None,
+ stacksize=None,
+ flags=None,
+ code=None,
+ consts=None,
+ names=None,
+ varnames=None,
+ filename=None,
+ name=None,
+ firstlineno=None,
+ lnotab=None):
+ if argcount is None: argcount = codeobject.co_argcount
+ if nlocals is None: nlocals = codeobject.co_nlocals
+ if stacksize is None: stacksize = codeobject.co_stacksize
+ if flags is None: flags = codeobject.co_flags
+ if code is None: code = codeobject.co_code
+ if consts is None: consts = codeobject.co_consts
+ if names is None: names = codeobject.co_names
+ if varnames is None: varnames = codeobject.co_varnames
+ if filename is None: filename = codeobject.co_filename
+ if name is None: name = codeobject.co_name
+ if firstlineno is None: firstlineno = codeobject.co_firstlineno
+ if lnotab is None: lnotab = codeobject.co_lnotab
+ return new.code(argcount,
+ nlocals,
+ stacksize,
+ flags,
+ code,
+ consts,
+ names,
+ varnames,
+ filename,
+ name,
+ firstlineno,
+ lnotab)
+
+code_attrs=['argcount',
+ 'nlocals',
+ 'stacksize',
+ 'flags',
+ 'code',
+ 'consts',
+ 'names',
+ 'varnames',
+ 'filename',
+ 'name',
+ 'firstlineno',
+ 'lnotab']
+
+
diff --git a/lib_pypy/pyrepl/curses.py b/lib_pypy/pyrepl/curses.py
new file mode 100644
index 0000000000..9bc6bb810d
--- /dev/null
+++ b/lib_pypy/pyrepl/curses.py
@@ -0,0 +1,39 @@
+
+# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
+# Armin Rigo
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Some try-import logic for two purposes: avoiding to bring in the whole
+# pure Python curses package if possible; and, in _curses is not actually
+# present, falling back to _minimal_curses (which is either a ctypes-based
+# pure Python module or a PyPy built-in module).
+try:
+ import _curses
+except ImportError:
+ try:
+ import _minimal_curses as _curses
+ except ImportError:
+ # Who knows, maybe some environment has "curses" but not "_curses".
+ # If not, at least the following import gives a clean ImportError.
+ import _curses
+
+setupterm = _curses.setupterm
+tigetstr = _curses.tigetstr
+tparm = _curses.tparm
+error = _curses.error
diff --git a/lib_pypy/pyrepl/fancy_termios.py b/lib_pypy/pyrepl/fancy_termios.py
new file mode 100644
index 0000000000..462f7c6005
--- /dev/null
+++ b/lib_pypy/pyrepl/fancy_termios.py
@@ -0,0 +1,52 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import termios
+
+class TermState:
+ def __init__(self, tuples):
+ self.iflag, self.oflag, self.cflag, self.lflag, \
+ self.ispeed, self.ospeed, self.cc = tuples
+ def as_list(self):
+ return [self.iflag, self.oflag, self.cflag, self.lflag,
+ self.ispeed, self.ospeed, self.cc]
+
+ def copy(self):
+ return self.__class__(self.as_list())
+
+def tcgetattr(fd):
+ return TermState(termios.tcgetattr(fd))
+
+def tcsetattr(fd, when, attrs):
+ termios.tcsetattr(fd, when, attrs.as_list())
+
+class Term(TermState):
+ TS__init__ = TermState.__init__
+ def __init__(self, fd=0):
+ self.TS__init__(termios.tcgetattr(fd))
+ self.fd = fd
+ self.stack = []
+ def save(self):
+ self.stack.append( self.as_list() )
+ def set(self, when=termios.TCSANOW):
+ termios.tcsetattr(self.fd, when, self.as_list())
+ def restore(self):
+ self.TS__init__(self.stack.pop())
+ self.set()
+
diff --git a/lib_pypy/pyrepl/historical_reader.py b/lib_pypy/pyrepl/historical_reader.py
new file mode 100644
index 0000000000..413e65f602
--- /dev/null
+++ b/lib_pypy/pyrepl/historical_reader.py
@@ -0,0 +1,311 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from pyrepl import reader, commands
+from pyrepl.reader import Reader as R
+
+isearch_keymap = tuple(
+ [('\\%03o'%c, 'isearch-end') for c in range(256) if chr(c) != '\\'] + \
+ [(c, 'isearch-add-character')
+ for c in map(chr, range(32, 127)) if c != '\\'] + \
+ [('\\%03o'%c, 'isearch-add-character')
+ for c in range(256) if chr(c).isalpha() and chr(c) != '\\'] + \
+ [('\\\\', 'self-insert'),
+ (r'\C-r', 'isearch-backwards'),
+ (r'\C-s', 'isearch-forwards'),
+ (r'\C-c', 'isearch-cancel'),
+ (r'\C-g', 'isearch-cancel'),
+ (r'\<backspace>', 'isearch-backspace')])
+
+del c
+
+ISEARCH_DIRECTION_NONE = ''
+ISEARCH_DIRECTION_BACKWARDS = 'r'
+ISEARCH_DIRECTION_FORWARDS = 'f'
+
+class next_history(commands.Command):
+ def do(self):
+ r = self.reader
+ if r.historyi == len(r.history):
+ r.error("end of history list")
+ return
+ r.select_item(r.historyi + 1)
+
+class previous_history(commands.Command):
+ def do(self):
+ r = self.reader
+ if r.historyi == 0:
+ r.error("start of history list")
+ return
+ r.select_item(r.historyi - 1)
+
+class restore_history(commands.Command):
+ def do(self):
+ r = self.reader
+ if r.historyi != len(r.history):
+ if r.get_unicode() != r.history[r.historyi]:
+ r.buffer = list(r.history[r.historyi])
+ r.pos = len(r.buffer)
+ r.dirty = 1
+
+class first_history(commands.Command):
+ def do(self):
+ self.reader.select_item(0)
+
+class last_history(commands.Command):
+ def do(self):
+ self.reader.select_item(len(self.reader.history))
+
+class operate_and_get_next(commands.FinishCommand):
+ def do(self):
+ self.reader.next_history = self.reader.historyi + 1
+
+class yank_arg(commands.Command):
+ def do(self):
+ r = self.reader
+ if r.last_command is self.__class__:
+ r.yank_arg_i += 1
+ else:
+ r.yank_arg_i = 0
+ if r.historyi < r.yank_arg_i:
+ r.error("beginning of history list")
+ return
+ a = r.get_arg(-1)
+ # XXX how to split?
+ words = r.get_item(r.historyi - r.yank_arg_i - 1).split()
+ if a < -len(words) or a >= len(words):
+ r.error("no such arg")
+ return
+ w = words[a]
+ b = r.buffer
+ if r.yank_arg_i > 0:
+ o = len(r.yank_arg_yanked)
+ else:
+ o = 0
+ b[r.pos - o:r.pos] = list(w)
+ r.yank_arg_yanked = w
+ r.pos += len(w) - o
+ r.dirty = 1
+
+class forward_history_isearch(commands.Command):
+ def do(self):
+ r = self.reader
+ r.isearch_direction = ISEARCH_DIRECTION_FORWARDS
+ r.isearch_start = r.historyi, r.pos
+ r.isearch_term = ''
+ r.dirty = 1
+ r.push_input_trans(r.isearch_trans)
+
+
+class reverse_history_isearch(commands.Command):
+ def do(self):
+ r = self.reader
+ r.isearch_direction = ISEARCH_DIRECTION_BACKWARDS
+ r.dirty = 1
+ r.isearch_term = ''
+ r.push_input_trans(r.isearch_trans)
+ r.isearch_start = r.historyi, r.pos
+
+class isearch_cancel(commands.Command):
+ def do(self):
+ r = self.reader
+ r.isearch_direction = ISEARCH_DIRECTION_NONE
+ r.pop_input_trans()
+ r.select_item(r.isearch_start[0])
+ r.pos = r.isearch_start[1]
+ r.dirty = 1
+
+class isearch_add_character(commands.Command):
+ def do(self):
+ r = self.reader
+ b = r.buffer
+ r.isearch_term += self.event[-1]
+ r.dirty = 1
+ p = r.pos + len(r.isearch_term) - 1
+ if b[p:p+1] != [r.isearch_term[-1]]:
+ r.isearch_next()
+
+class isearch_backspace(commands.Command):
+ def do(self):
+ r = self.reader
+ if len(r.isearch_term) > 0:
+ r.isearch_term = r.isearch_term[:-1]
+ r.dirty = 1
+ else:
+ r.error("nothing to rubout")
+
+class isearch_forwards(commands.Command):
+ def do(self):
+ r = self.reader
+ r.isearch_direction = ISEARCH_DIRECTION_FORWARDS
+ r.isearch_next()
+
+class isearch_backwards(commands.Command):
+ def do(self):
+ r = self.reader
+ r.isearch_direction = ISEARCH_DIRECTION_BACKWARDS
+ r.isearch_next()
+
+class isearch_end(commands.Command):
+ def do(self):
+ r = self.reader
+ r.isearch_direction = ISEARCH_DIRECTION_NONE
+ r.console.forgetinput()
+ r.pop_input_trans()
+ r.dirty = 1
+
+class HistoricalReader(R):
+ """Adds history support (with incremental history searching) to the
+ Reader class.
+
+ Adds the following instance variables:
+ * history:
+ a list of strings
+ * historyi:
+ * transient_history:
+ * next_history:
+ * isearch_direction, isearch_term, isearch_start:
+ * yank_arg_i, yank_arg_yanked:
+ used by the yank-arg command; not actually manipulated by any
+ HistoricalReader instance methods.
+ """
+
+ def collect_keymap(self):
+ return super(HistoricalReader, self).collect_keymap() + (
+ (r'\C-n', 'next-history'),
+ (r'\C-p', 'previous-history'),
+ (r'\C-o', 'operate-and-get-next'),
+ (r'\C-r', 'reverse-history-isearch'),
+ (r'\C-s', 'forward-history-isearch'),
+ (r'\M-r', 'restore-history'),
+ (r'\M-.', 'yank-arg'),
+ (r'\<page down>', 'last-history'),
+ (r'\<page up>', 'first-history'))
+
+
+ def __init__(self, console):
+ super(HistoricalReader, self).__init__(console)
+ self.history = []
+ self.historyi = 0
+ self.transient_history = {}
+ self.next_history = None
+ self.isearch_direction = ISEARCH_DIRECTION_NONE
+ for c in [next_history, previous_history, restore_history,
+ first_history, last_history, yank_arg,
+ forward_history_isearch, reverse_history_isearch,
+ isearch_end, isearch_add_character, isearch_cancel,
+ isearch_add_character, isearch_backspace,
+ isearch_forwards, isearch_backwards, operate_and_get_next]:
+ self.commands[c.__name__] = c
+ self.commands[c.__name__.replace('_', '-')] = c
+ from pyrepl import input
+ self.isearch_trans = input.KeymapTranslator(
+ isearch_keymap, invalid_cls=isearch_end,
+ character_cls=isearch_add_character)
+
+ def select_item(self, i):
+ self.transient_history[self.historyi] = self.get_unicode()
+ buf = self.transient_history.get(i)
+ if buf is None:
+ buf = self.history[i]
+ self.buffer = list(buf)
+ self.historyi = i
+ self.pos = len(self.buffer)
+ self.dirty = 1
+
+ def get_item(self, i):
+ if i <> len(self.history):
+ return self.transient_history.get(i, self.history[i])
+ else:
+ return self.transient_history.get(i, self.get_unicode())
+
+ def prepare(self):
+ super(HistoricalReader, self).prepare()
+ try:
+ self.transient_history = {}
+ if self.next_history is not None \
+ and self.next_history < len(self.history):
+ self.historyi = self.next_history
+ self.buffer[:] = list(self.history[self.next_history])
+ self.pos = len(self.buffer)
+ self.transient_history[len(self.history)] = ''
+ else:
+ self.historyi = len(self.history)
+ self.next_history = None
+ except:
+ self.restore()
+ raise
+
+ def get_prompt(self, lineno, cursor_on_line):
+ if cursor_on_line and self.isearch_direction <> ISEARCH_DIRECTION_NONE:
+ d = 'rf'[self.isearch_direction == ISEARCH_DIRECTION_FORWARDS]
+ return "(%s-search `%s') "%(d, self.isearch_term)
+ else:
+ return super(HistoricalReader, self).get_prompt(lineno, cursor_on_line)
+
+ def isearch_next(self):
+ st = self.isearch_term
+ p = self.pos
+ i = self.historyi
+ s = self.get_unicode()
+ forwards = self.isearch_direction == ISEARCH_DIRECTION_FORWARDS
+ while 1:
+ if forwards:
+ p = s.find(st, p + 1)
+ else:
+ p = s.rfind(st, 0, p + len(st) - 1)
+ if p != -1:
+ self.select_item(i)
+ self.pos = p
+ return
+ elif ((forwards and i == len(self.history) - 1)
+ or (not forwards and i == 0)):
+ self.error("not found")
+ return
+ else:
+ if forwards:
+ i += 1
+ s = self.get_item(i)
+ p = -1
+ else:
+ i -= 1
+ s = self.get_item(i)
+ p = len(s)
+
+ def finish(self):
+ super(HistoricalReader, self).finish()
+ ret = self.get_unicode()
+ for i, t in self.transient_history.items():
+ if i < len(self.history) and i != self.historyi:
+ self.history[i] = t
+ if ret:
+ self.history.append(ret)
+
+def test():
+ from pyrepl.unix_console import UnixConsole
+ reader = HistoricalReader(UnixConsole())
+ reader.ps1 = "h**> "
+ reader.ps2 = "h/*> "
+ reader.ps3 = "h|*> "
+ reader.ps4 = "h\*> "
+ while reader.readline():
+ pass
+
+if __name__=='__main__':
+ test()
diff --git a/lib_pypy/pyrepl/input.py b/lib_pypy/pyrepl/input.py
new file mode 100644
index 0000000000..673f791a4d
--- /dev/null
+++ b/lib_pypy/pyrepl/input.py
@@ -0,0 +1,97 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# (naming modules after builtin functions is not such a hot idea...)
+
+# an KeyTrans instance translates Event objects into Command objects
+
+# hmm, at what level do we want [C-i] and [tab] to be equivalent?
+# [meta-a] and [esc a]? obviously, these are going to be equivalent
+# for the UnixConsole, but should they be for PygameConsole?
+
+# it would in any situation seem to be a bad idea to bind, say, [tab]
+# and [C-i] to *different* things... but should binding one bind the
+# other?
+
+# executive, temporary decision: [tab] and [C-i] are distinct, but
+# [meta-key] is identified with [esc key]. We demand that any console
+# class does quite a lot towards emulating a unix terminal.
+
+from pyrepl import unicodedata_
+
+class InputTranslator(object):
+ def push(self, evt):
+ pass
+ def get(self):
+ pass
+ def empty(self):
+ pass
+
+class KeymapTranslator(InputTranslator):
+ def __init__(self, keymap, verbose=0,
+ invalid_cls=None, character_cls=None):
+ self.verbose = verbose
+ from pyrepl.keymap import compile_keymap, parse_keys
+ self.keymap = keymap
+ self.invalid_cls = invalid_cls
+ self.character_cls = character_cls
+ d = {}
+ for keyspec, command in keymap:
+ keyseq = tuple(parse_keys(keyspec))
+ d[keyseq] = command
+ if self.verbose:
+ print d
+ self.k = self.ck = compile_keymap(d, ())
+ self.results = []
+ self.stack = []
+ def push(self, evt):
+ if self.verbose:
+ print "pushed", evt.data,
+ key = evt.data
+ d = self.k.get(key)
+ if isinstance(d, dict):
+ if self.verbose:
+ print "transition"
+ self.stack.append(key)
+ self.k = d
+ else:
+ if d is None:
+ if self.verbose:
+ print "invalid"
+ if self.stack or len(key) > 1 or unicodedata_.category(key) == 'C':
+ self.results.append(
+ (self.invalid_cls, self.stack + [key]))
+ else:
+ # small optimization:
+ self.k[key] = self.character_cls
+ self.results.append(
+ (self.character_cls, [key]))
+ else:
+ if self.verbose:
+ print "matched", d
+ self.results.append((d, self.stack + [key]))
+ self.stack = []
+ self.k = self.ck
+ def get(self):
+ if self.results:
+ return self.results.pop(0)
+ else:
+ return None
+ def empty(self):
+ return not self.results
diff --git a/lib_pypy/pyrepl/keymap.py b/lib_pypy/pyrepl/keymap.py
new file mode 100644
index 0000000000..20c7c2928a
--- /dev/null
+++ b/lib_pypy/pyrepl/keymap.py
@@ -0,0 +1,186 @@
+# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
+# Armin Rigo
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+functions for parsing keyspecs
+
+Support for turning keyspecs into appropriate sequences.
+
+pyrepl uses it's own bastardized keyspec format, which is meant to be
+a strict superset of readline's \"KEYSEQ\" format (which is to say
+that if you can come up with a spec readline accepts that this
+doesn't, you've found a bug and should tell me about it).
+
+Note that this is the `\\C-o' style of readline keyspec, not the
+`Control-o' sort.
+
+A keyspec is a string representing a sequence of keypresses that can
+be bound to a command.
+
+All characters other than the backslash represent themselves. In the
+traditional manner, a backslash introduces a escape sequence.
+
+The extension to readline is that the sequence \\<KEY> denotes the
+sequence of charaters produced by hitting KEY.
+
+Examples:
+
+`a' - what you get when you hit the `a' key
+`\\EOA' - Escape - O - A (up, on my terminal)
+`\\<UP>' - the up arrow key
+`\\<up>' - ditto (keynames are case insensitive)
+`\\C-o', `\\c-o' - control-o
+`\\M-.' - meta-period
+`\\E.' - ditto (that's how meta works for pyrepl)
+`\\<tab>', `\\<TAB>', `\\t', `\\011', '\\x09', '\\X09', '\\C-i', '\\C-I'
+ - all of these are the tab character. Can you think of any more?
+"""
+
+_escapes = {
+ '\\':'\\',
+ "'":"'",
+ '"':'"',
+ 'a':'\a',
+ 'b':'\h',
+ 'e':'\033',
+ 'f':'\f',
+ 'n':'\n',
+ 'r':'\r',
+ 't':'\t',
+ 'v':'\v'
+ }
+
+_keynames = {
+ 'backspace': 'backspace',
+ 'delete': 'delete',
+ 'down': 'down',
+ 'end': 'end',
+ 'enter': '\r',
+ 'escape': '\033',
+ 'f1' : 'f1', 'f2' : 'f2', 'f3' : 'f3', 'f4' : 'f4',
+ 'f5' : 'f5', 'f6' : 'f6', 'f7' : 'f7', 'f8' : 'f8',
+ 'f9' : 'f9', 'f10': 'f10', 'f11': 'f11', 'f12': 'f12',
+ 'f13': 'f13', 'f14': 'f14', 'f15': 'f15', 'f16': 'f16',
+ 'f17': 'f17', 'f18': 'f18', 'f19': 'f19', 'f20': 'f20',
+ 'home': 'home',
+ 'insert': 'insert',
+ 'left': 'left',
+ 'page down': 'page down',
+ 'page up': 'page up',
+ 'return': '\r',
+ 'right': 'right',
+ 'space': ' ',
+ 'tab': '\t',
+ 'up': 'up',
+ }
+
+class KeySpecError(Exception):
+ pass
+
+def _parse_key1(key, s):
+ ctrl = 0
+ meta = 0
+ ret = ''
+ while not ret and s < len(key):
+ if key[s] == '\\':
+ c = key[s+1].lower()
+ if _escapes.has_key(c):
+ ret = _escapes[c]
+ s += 2
+ elif c == "c":
+ if key[s + 2] != '-':
+ raise KeySpecError, \
+ "\\C must be followed by `-' (char %d of %s)"%(
+ s + 2, repr(key))
+ if ctrl:
+ raise KeySpecError, "doubled \\C- (char %d of %s)"%(
+ s + 1, repr(key))
+ ctrl = 1
+ s += 3
+ elif c == "m":
+ if key[s + 2] != '-':
+ raise KeySpecError, \
+ "\\M must be followed by `-' (char %d of %s)"%(
+ s + 2, repr(key))
+ if meta:
+ raise KeySpecError, "doubled \\M- (char %d of %s)"%(
+ s + 1, repr(key))
+ meta = 1
+ s += 3
+ elif c.isdigit():
+ n = key[s+1:s+4]
+ ret = chr(int(n, 8))
+ s += 4
+ elif c == 'x':
+ n = key[s+2:s+4]
+ ret = chr(int(n, 16))
+ s += 4
+ elif c == '<':
+ t = key.find('>', s)
+ if t == -1:
+ raise KeySpecError, \
+ "unterminated \\< starting at char %d of %s"%(
+ s + 1, repr(key))
+ ret = key[s+2:t].lower()
+ if ret not in _keynames:
+ raise KeySpecError, \
+ "unrecognised keyname `%s' at char %d of %s"%(
+ ret, s + 2, repr(key))
+ ret = _keynames[ret]
+ s = t + 1
+ else:
+ raise KeySpecError, \
+ "unknown backslash escape %s at char %d of %s"%(
+ `c`, s + 2, repr(key))
+ else:
+ ret = key[s]
+ s += 1
+ if ctrl:
+ if len(ret) > 1:
+ raise KeySpecError, "\\C- must be followed by a character"
+ ret = chr(ord(ret) & 0x1f) # curses.ascii.ctrl()
+ if meta:
+ ret = ['\033', ret]
+ else:
+ ret = [ret]
+ return ret, s
+
+def parse_keys(key):
+ s = 0
+ r = []
+ while s < len(key):
+ k, s = _parse_key1(key, s)
+ r.extend(k)
+ return r
+
+def compile_keymap(keymap, empty=''):
+ r = {}
+ for key, value in keymap.items():
+ r.setdefault(key[0], {})[key[1:]] = value
+ for key, value in r.items():
+ if empty in value:
+ if len(value) <> 1:
+ raise KeySpecError, \
+ "key definitions for %s clash"%(value.values(),)
+ else:
+ r[key] = value[empty]
+ else:
+ r[key] = compile_keymap(value, empty)
+ return r
diff --git a/lib_pypy/pyrepl/keymaps.py b/lib_pypy/pyrepl/keymaps.py
new file mode 100644
index 0000000000..76ba896ca8
--- /dev/null
+++ b/lib_pypy/pyrepl/keymaps.py
@@ -0,0 +1,140 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+reader_emacs_keymap = tuple(
+ [(r'\C-a', 'beginning-of-line'),
+ (r'\C-b', 'left'),
+ (r'\C-c', 'interrupt'),
+ (r'\C-d', 'delete'),
+ (r'\C-e', 'end-of-line'),
+ (r'\C-f', 'right'),
+ (r'\C-g', 'cancel'),
+ (r'\C-h', 'backspace'),
+ (r'\C-j', 'self-insert'),
+ (r'\<return>', 'accept'),
+ (r'\C-k', 'kill-line'),
+ (r'\C-l', 'clear-screen'),
+# (r'\C-m', 'accept'),
+ (r'\C-q', 'quoted-insert'),
+ (r'\C-t', 'transpose-characters'),
+ (r'\C-u', 'unix-line-discard'),
+ (r'\C-v', 'quoted-insert'),
+ (r'\C-w', 'unix-word-rubout'),
+ (r'\C-x\C-u', 'upcase-region'),
+ (r'\C-y', 'yank'),
+ (r'\C-z', 'suspend'),
+
+ (r'\M-b', 'backward-word'),
+ (r'\M-c', 'capitalize-word'),
+ (r'\M-d', 'kill-word'),
+ (r'\M-f', 'forward-word'),
+ (r'\M-l', 'downcase-word'),
+ (r'\M-t', 'transpose-words'),
+ (r'\M-u', 'upcase-word'),
+ (r'\M-y', 'yank-pop'),
+ (r'\M--', 'digit-arg'),
+ (r'\M-0', 'digit-arg'),
+ (r'\M-1', 'digit-arg'),
+ (r'\M-2', 'digit-arg'),
+ (r'\M-3', 'digit-arg'),
+ (r'\M-4', 'digit-arg'),
+ (r'\M-5', 'digit-arg'),
+ (r'\M-6', 'digit-arg'),
+ (r'\M-7', 'digit-arg'),
+ (r'\M-8', 'digit-arg'),
+ (r'\M-9', 'digit-arg'),
+ (r'\M-\n', 'self-insert'),
+ (r'\<backslash>', 'self-insert')] + \
+ [(c, 'self-insert')
+ for c in map(chr, range(32, 127)) if c <> '\\'] + \
+ [(c, 'self-insert')
+ for c in map(chr, range(128, 256)) if c.isalpha()] + \
+ [(r'\<up>', 'up'),
+ (r'\<down>', 'down'),
+ (r'\<left>', 'left'),
+ (r'\<right>', 'right'),
+ (r'\<insert>', 'quoted-insert'),
+ (r'\<delete>', 'delete'),
+ (r'\<backspace>', 'backspace'),
+ (r'\M-\<backspace>', 'backward-kill-word'),
+ (r'\<end>', 'end'),
+ (r'\<home>', 'home'),
+ (r'\<f1>', 'help'),
+ (r'\EOF', 'end'), # the entries in the terminfo database for xterms
+ (r'\EOH', 'home'), # seem to be wrong. this is a less than ideal
+ # workaround
+ ])
+
+hist_emacs_keymap = reader_emacs_keymap + (
+ (r'\C-n', 'next-history'),
+ (r'\C-p', 'previous-history'),
+ (r'\C-o', 'operate-and-get-next'),
+ (r'\C-r', 'reverse-history-isearch'),
+ (r'\C-s', 'forward-history-isearch'),
+ (r'\M-r', 'restore-history'),
+ (r'\M-.', 'yank-arg'),
+ (r'\<page down>', 'last-history'),
+ (r'\<page up>', 'first-history'))
+
+comp_emacs_keymap = hist_emacs_keymap + (
+ (r'\t', 'complete'),)
+
+python_emacs_keymap = comp_emacs_keymap + (
+ (r'\n', 'maybe-accept'),
+ (r'\M-\n', 'self-insert'))
+
+reader_vi_insert_keymap = tuple(
+ [(c, 'self-insert')
+ for c in map(chr, range(32, 127)) if c <> '\\'] + \
+ [(c, 'self-insert')
+ for c in map(chr, range(128, 256)) if c.isalpha()] + \
+ [(r'\C-d', 'delete'),
+ (r'\<backspace>', 'backspace'),
+ ('')])
+
+reader_vi_command_keymap = tuple(
+ [
+ ('E', 'enter-emacs-mode'),
+ ('R', 'enter-replace-mode'),
+ ('dw', 'delete-word'),
+ ('dd', 'delete-line'),
+
+ ('h', 'left'),
+ ('i', 'enter-insert-mode'),
+ ('j', 'down'),
+ ('k', 'up'),
+ ('l', 'right'),
+ ('r', 'replace-char'),
+ ('w', 'forward-word'),
+ ('x', 'delete'),
+ ('.', 'repeat-edit'), # argh!
+ (r'\<insert>', 'enter-insert-mode'),
+ ] +
+ [(c, 'digit-arg') for c in '01234567689'] +
+ [])
+
+
+reader_keymaps = {
+ 'emacs' : reader_emacs_keymap,
+ 'vi-insert' : reader_vi_insert_keymap,
+ 'vi-command' : reader_vi_command_keymap
+ }
+
+del c # from the listcomps
+
diff --git a/lib_pypy/pyrepl/module_lister.py b/lib_pypy/pyrepl/module_lister.py
new file mode 100644
index 0000000000..9afdf22ba6
--- /dev/null
+++ b/lib_pypy/pyrepl/module_lister.py
@@ -0,0 +1,70 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from pyrepl.completing_reader import uniqify
+import os, sys
+
+# for the completion support.
+# this is all quite nastily written.
+_packages = {}
+
+def _make_module_list_dir(dir, suffs, prefix=''):
+ l = []
+ for fname in os.listdir(dir):
+ file = os.path.join(dir, fname)
+ if os.path.isfile(file):
+ for suff in suffs:
+ if fname.endswith(suff):
+ l.append( prefix + fname[:-len(suff)] )
+ break
+ elif os.path.isdir(file) \
+ and os.path.exists(os.path.join(file, "__init__.py")):
+ l.append( prefix + fname )
+ _packages[prefix + fname] = _make_module_list_dir(
+ file, suffs, prefix + fname + '.' )
+ l = uniqify(l)
+ l.sort()
+ return l
+
+def _make_module_list():
+ import imp
+ suffs = [x[0] for x in imp.get_suffixes() if x[0] != '.pyc']
+ def compare(x, y):
+ c = -cmp(len(x), len(y))
+ if c:
+ return c
+ else:
+ return -cmp(x, y)
+ suffs.sort(compare)
+ _packages[''] = list(sys.builtin_module_names)
+ for dir in sys.path:
+ if dir == '':
+ dir = '.'
+ if os.path.isdir(dir):
+ _packages[''] += _make_module_list_dir(dir, suffs)
+ _packages[''].sort()
+
+def find_modules(stem):
+ l = stem.split('.')
+ pack = '.'.join(l[:-1])
+ try:
+ mods = _packages[pack]
+ except KeyError:
+ raise ImportError, "can't find \"%s\" package"%pack
+ return [mod for mod in mods if mod.startswith(stem)]
diff --git a/lib_pypy/pyrepl/pygame_console.py b/lib_pypy/pyrepl/pygame_console.py
new file mode 100644
index 0000000000..cb90b8b512
--- /dev/null
+++ b/lib_pypy/pyrepl/pygame_console.py
@@ -0,0 +1,353 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# the pygame console is currently thoroughly broken.
+
+# there's a fundamental difference from the UnixConsole: here we're
+# the terminal emulator too, in effect. This means, e.g., for pythoni
+# we really need a separate process (or thread) to monitor for ^C
+# during command execution and zap the executor process. Making this
+# work on non-Unix is expected to be even more entertaining.
+
+from pygame.locals import *
+from pyrepl.console import Console, Event
+from pyrepl import pygame_keymap
+import pygame
+import types
+
+lmargin = 5
+rmargin = 5
+tmargin = 5
+bmargin = 5
+
+try:
+ bool
+except NameError:
+ def bool(x):
+ return not not x
+
+modcolors = {K_LCTRL:1,
+ K_RCTRL:1,
+ K_LMETA:1,
+ K_RMETA:1,
+ K_LALT:1,
+ K_RALT:1,
+ K_LSHIFT:1,
+ K_RSHIFT:1}
+
+class colors:
+ fg = 250,240,230
+ bg = 5, 5, 5
+ cursor = 230, 0, 230
+ margin = 5, 5, 15
+
+class FakeStdout:
+ def __init__(self, con):
+ self.con = con
+ def write(self, text):
+ self.con.write(text)
+ def flush(self):
+ pass
+
+class FakeStdin:
+ def __init__(self, con):
+ self.con = con
+ def read(self, n=None):
+ # argh!
+ raise NotImplementedError
+ def readline(self, n=None):
+ from reader import Reader
+ try:
+ # this isn't quite right: it will clobber any prompt that's
+ # been printed. Not sure how to get around this...
+ return Reader(self.con).readline()
+ except EOFError:
+ return ''
+
+class PyGameConsole(Console):
+ """Attributes:
+
+ (keymap),
+ (fd),
+ screen,
+ height,
+ width,
+ """
+
+ def __init__(self):
+ self.pygame_screen = pygame.display.set_mode((800, 600))
+ pygame.font.init()
+ pygame.key.set_repeat(500, 30)
+ self.font = pygame.font.Font(
+ "/usr/X11R6/lib/X11/fonts/TTF/luximr.ttf", 15)
+ self.fw, self.fh = self.fontsize = self.font.size("X")
+ self.cursor = pygame.Surface(self.fontsize)
+ self.cursor.fill(colors.cursor)
+ self.clear()
+ self.curs_vis = 1
+ self.height, self.width = self.getheightwidth()
+ pygame.display.update()
+ pygame.event.set_allowed(None)
+ pygame.event.set_allowed(KEYDOWN)
+
+ def install_keymap(self, keymap):
+ """Install a given keymap.
+
+ keymap is a tuple of 2-element tuples; each small tuple is a
+ pair (keyspec, event-name). The format for keyspec is
+ modelled on that used by readline (so read that manual for
+ now!)."""
+ self.k = self.keymap = pygame_keymap.compile_keymap(keymap)
+
+ def char_rect(self, x, y):
+ return self.char_pos(x, y), self.fontsize
+
+ def char_pos(self, x, y):
+ return (lmargin + x*self.fw,
+ tmargin + y*self.fh + self.cur_top + self.scroll)
+
+ def paint_margin(self):
+ s = self.pygame_screen
+ c = colors.margin
+ s.fill(c, [0, 0, 800, tmargin])
+ s.fill(c, [0, 0, lmargin, 600])
+ s.fill(c, [0, 600 - bmargin, 800, bmargin])
+ s.fill(c, [800 - rmargin, 0, lmargin, 600])
+
+ def refresh(self, screen, (cx, cy)):
+ self.screen = screen
+ self.pygame_screen.fill(colors.bg,
+ [0, tmargin + self.cur_top + self.scroll,
+ 800, 600])
+ self.paint_margin()
+
+ line_top = self.cur_top
+ width, height = self.fontsize
+ self.cxy = (cx, cy)
+ cp = self.char_pos(cx, cy)
+ if cp[1] < tmargin:
+ self.scroll = - (cy*self.fh + self.cur_top)
+ self.repaint()
+ elif cp[1] + self.fh > 600 - bmargin:
+ self.scroll += (600 - bmargin) - (cp[1] + self.fh)
+ self.repaint()
+ if self.curs_vis:
+ self.pygame_screen.blit(self.cursor, self.char_pos(cx, cy))
+ for line in screen:
+ if 0 <= line_top + self.scroll <= (600 - bmargin - tmargin - self.fh):
+ if line:
+ ren = self.font.render(line, 1, colors.fg)
+ self.pygame_screen.blit(ren, (lmargin,
+ tmargin + line_top + self.scroll))
+ line_top += self.fh
+ pygame.display.update()
+
+ def prepare(self):
+ self.cmd_buf = ''
+ self.k = self.keymap
+ self.height, self.width = self.getheightwidth()
+ self.curs_vis = 1
+ self.cur_top = self.pos[0]
+ self.event_queue = []
+
+ def restore(self):
+ pass
+
+ def blit_a_char(self, linen, charn):
+ line = self.screen[linen]
+ if charn < len(line):
+ text = self.font.render(line[charn], 1, colors.fg)
+ self.pygame_screen.blit(text, self.char_pos(charn, linen))
+
+ def move_cursor(self, x, y):
+ cp = self.char_pos(x, y)
+ if cp[1] < tmargin or cp[1] + self.fh > 600 - bmargin:
+ self.event_queue.append(Event('refresh', '', ''))
+ else:
+ if self.curs_vis:
+ cx, cy = self.cxy
+ self.pygame_screen.fill(colors.bg, self.char_rect(cx, cy))
+ self.blit_a_char(cy, cx)
+ self.pygame_screen.blit(self.cursor, cp)
+ self.blit_a_char(y, x)
+ pygame.display.update()
+ self.cxy = (x, y)
+
+ def set_cursor_vis(self, vis):
+ self.curs_vis = vis
+ if vis:
+ self.move_cursor(*self.cxy)
+ else:
+ cx, cy = self.cxy
+ self.pygame_screen.fill(colors.bg, self.char_rect(cx, cy))
+ self.blit_a_char(cy, cx)
+ pygame.display.update()
+
+ def getheightwidth(self):
+ """Return (height, width) where height and width are the height
+ and width of the terminal window in characters."""
+ return ((600 - tmargin - bmargin)/self.fh,
+ (800 - lmargin - rmargin)/self.fw)
+
+ def tr_event(self, pyg_event):
+ shift = bool(pyg_event.mod & KMOD_SHIFT)
+ ctrl = bool(pyg_event.mod & KMOD_CTRL)
+ meta = bool(pyg_event.mod & (KMOD_ALT|KMOD_META))
+
+ try:
+ return self.k[(pyg_event.unicode, meta, ctrl)], pyg_event.unicode
+ except KeyError:
+ try:
+ return self.k[(pyg_event.key, meta, ctrl)], pyg_event.unicode
+ except KeyError:
+ return "invalid-key", pyg_event.unicode
+
+ def get_event(self, block=1):
+ """Return an Event instance. Returns None if |block| is false
+ and there is no event pending, otherwise waits for the
+ completion of an event."""
+ while 1:
+ if self.event_queue:
+ return self.event_queue.pop(0)
+ elif block:
+ pyg_event = pygame.event.wait()
+ else:
+ pyg_event = pygame.event.poll()
+ if pyg_event.type == NOEVENT:
+ return
+
+ if pyg_event.key in modcolors:
+ continue
+
+ k, c = self.tr_event(pyg_event)
+ self.cmd_buf += c.encode('ascii', 'replace')
+ self.k = k
+
+ if not isinstance(k, types.DictType):
+ e = Event(k, self.cmd_buf, [])
+ self.k = self.keymap
+ self.cmd_buf = ''
+ return e
+
+ def beep(self):
+ # uhh, can't be bothered now.
+ # pygame.sound.something, I guess.
+ pass
+
+ def clear(self):
+ """Wipe the screen"""
+ self.pygame_screen.fill(colors.bg)
+ #self.screen = []
+ self.pos = [0, 0]
+ self.grobs = []
+ self.cur_top = 0
+ self.scroll = 0
+
+ def finish(self):
+ """Move the cursor to the end of the display and otherwise get
+ ready for end. XXX could be merged with restore? Hmm."""
+ if self.curs_vis:
+ cx, cy = self.cxy
+ self.pygame_screen.fill(colors.bg, self.char_rect(cx, cy))
+ self.blit_a_char(cy, cx)
+ for line in self.screen:
+ self.write_line(line, 1)
+ if self.curs_vis:
+ self.pygame_screen.blit(self.cursor,
+ (lmargin + self.pos[1],
+ tmargin + self.pos[0] + self.scroll))
+ pygame.display.update()
+
+ def flushoutput(self):
+ """Flush all output to the screen (assuming there's some
+ buffering going on somewhere)"""
+ # no buffering here, ma'am (though perhaps there should be!)
+ pass
+
+ def forgetinput(self):
+ """Forget all pending, but not yet processed input."""
+ while pygame.event.poll().type <> NOEVENT:
+ pass
+
+ def getpending(self):
+ """Return the characters that have been typed but not yet
+ processed."""
+ events = []
+ while 1:
+ event = pygame.event.poll()
+ if event.type == NOEVENT:
+ break
+ events.append(event)
+
+ return events
+
+ def wait(self):
+ """Wait for an event."""
+ raise Exception, "erp!"
+
+ def repaint(self):
+ # perhaps we should consolidate grobs?
+ self.pygame_screen.fill(colors.bg)
+ self.paint_margin()
+ for (y, x), surf, text in self.grobs:
+ if surf and 0 < y + self.scroll:
+ self.pygame_screen.blit(surf, (lmargin + x,
+ tmargin + y + self.scroll))
+ pygame.display.update()
+
+ def write_line(self, line, ret):
+ charsleft = (self.width*self.fw - self.pos[1])/self.fw
+ while len(line) > charsleft:
+ self.write_line(line[:charsleft], 1)
+ line = line[charsleft:]
+ if line:
+ ren = self.font.render(line, 1, colors.fg, colors.bg)
+ self.grobs.append((self.pos[:], ren, line))
+ self.pygame_screen.blit(ren,
+ (lmargin + self.pos[1],
+ tmargin + self.pos[0] + self.scroll))
+ else:
+ self.grobs.append((self.pos[:], None, line))
+ if ret:
+ self.pos[0] += self.fh
+ if tmargin + self.pos[0] + self.scroll + self.fh > 600 - bmargin:
+ self.scroll = 600 - bmargin - self.pos[0] - self.fh - tmargin
+ self.repaint()
+ self.pos[1] = 0
+ else:
+ self.pos[1] += self.fw*len(line)
+
+ def write(self, text):
+ lines = text.split("\n")
+ if self.curs_vis:
+ self.pygame_screen.fill(colors.bg,
+ (lmargin + self.pos[1],
+ tmargin + self.pos[0] + self.scroll,
+ self.fw, self.fh))
+ for line in lines[:-1]:
+ self.write_line(line, 1)
+ self.write_line(lines[-1], 0)
+ if self.curs_vis:
+ self.pygame_screen.blit(self.cursor,
+ (lmargin + self.pos[1],
+ tmargin + self.pos[0] + self.scroll))
+ pygame.display.update()
+
+ def flush(self):
+ pass
diff --git a/lib_pypy/pyrepl/pygame_keymap.py b/lib_pypy/pyrepl/pygame_keymap.py
new file mode 100644
index 0000000000..5531f1c7e6
--- /dev/null
+++ b/lib_pypy/pyrepl/pygame_keymap.py
@@ -0,0 +1,250 @@
+# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
+# Armin Rigo
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# keyspec parsing for a pygame console. currently this is simply copy
+# n' change from the unix (ie. trad terminal) variant; probably some
+# refactoring will happen when I work out how it will work best.
+
+# A key is represented as *either*
+
+# a) a (keycode, meta, ctrl) sequence (used for special keys such as
+# f1, the up arrow key, etc)
+# b) a (unichar, meta, ctrl) sequence (used for printable chars)
+
+# Because we allow keystokes like '\\C-xu', I'll use the same trick as
+# the unix keymap module uses.
+
+# '\\C-a' --> (K_a, 0, 1)
+
+# XXX it's actually possible to test this module, so it should have a
+# XXX test suite.
+
+from pygame.locals import *
+
+_escapes = {
+ '\\': K_BACKSLASH,
+ "'" : K_QUOTE,
+ '"' : K_QUOTEDBL,
+# 'a' : '\a',
+ 'b' : K_BACKSLASH,
+ 'e' : K_ESCAPE,
+# 'f' : '\f',
+ 'n' : K_RETURN,
+ 'r' : K_RETURN,
+ 't' : K_TAB,
+# 'v' : '\v'
+ }
+
+_keynames = {
+ 'backspace' : K_BACKSPACE,
+ 'delete' : K_DELETE,
+ 'down' : K_DOWN,
+ 'end' : K_END,
+ 'enter' : K_KP_ENTER,
+ 'escape' : K_ESCAPE,
+ 'f1' : K_F1, 'f2' : K_F2, 'f3' : K_F3, 'f4' : K_F4,
+ 'f5' : K_F5, 'f6' : K_F6, 'f7' : K_F7, 'f8' : K_F8,
+ 'f9' : K_F9, 'f10': K_F10,'f11': K_F11,'f12': K_F12,
+ 'f13': K_F13,'f14': K_F14,'f15': K_F15,
+ 'home' : K_HOME,
+ 'insert' : K_INSERT,
+ 'left' : K_LEFT,
+ 'pgdown' : K_PAGEDOWN, 'page down' : K_PAGEDOWN,
+ 'pgup' : K_PAGEUP, 'page up' : K_PAGEUP,
+ 'return' : K_RETURN,
+ 'right' : K_RIGHT,
+ 'space' : K_SPACE,
+ 'tab' : K_TAB,
+ 'up' : K_UP,
+ }
+
+class KeySpecError(Exception):
+ pass
+
+def _parse_key1(key, s):
+ ctrl = 0
+ meta = 0
+ ret = ''
+ while not ret and s < len(key):
+ if key[s] == '\\':
+ c = key[s+1].lower()
+ if _escapes.has_key(c):
+ ret = _escapes[c]
+ s += 2
+ elif c == "c":
+ if key[s + 2] != '-':
+ raise KeySpecError, \
+ "\\C must be followed by `-' (char %d of %s)"%(
+ s + 2, repr(key))
+ if ctrl:
+ raise KeySpecError, "doubled \\C- (char %d of %s)"%(
+ s + 1, repr(key))
+ ctrl = 1
+ s += 3
+ elif c == "m":
+ if key[s + 2] != '-':
+ raise KeySpecError, \
+ "\\M must be followed by `-' (char %d of %s)"%(
+ s + 2, repr(key))
+ if meta:
+ raise KeySpecError, "doubled \\M- (char %d of %s)"%(
+ s + 1, repr(key))
+ meta = 1
+ s += 3
+ elif c.isdigit():
+ n = key[s+1:s+4]
+ ret = chr(int(n, 8))
+ s += 4
+ elif c == 'x':
+ n = key[s+2:s+4]
+ ret = chr(int(n, 16))
+ s += 4
+ elif c == '<':
+ t = key.find('>', s)
+ if t == -1:
+ raise KeySpecError, \
+ "unterminated \\< starting at char %d of %s"%(
+ s + 1, repr(key))
+ try:
+ ret = _keynames[key[s+2:t].lower()]
+ s = t + 1
+ except KeyError:
+ raise KeySpecError, \
+ "unrecognised keyname `%s' at char %d of %s"%(
+ key[s+2:t], s + 2, repr(key))
+ if ret is None:
+ return None, s
+ else:
+ raise KeySpecError, \
+ "unknown backslash escape %s at char %d of %s"%(
+ `c`, s + 2, repr(key))
+ else:
+ if ctrl:
+ ret = chr(ord(key[s]) & 0x1f) # curses.ascii.ctrl()
+ ret = unicode(ret)
+ else:
+ ret = unicode(key[s])
+ s += 1
+ return (ret, meta, ctrl), s
+
+def parse_keys(key):
+ s = 0
+ r = []
+ while s < len(key):
+ k, s = _parse_key1(key, s)
+ if k is None:
+ return None
+ r.append(k)
+ return tuple(r)
+
+def _compile_keymap(keymap):
+ r = {}
+ for key, value in keymap.items():
+ r.setdefault(key[0], {})[key[1:]] = value
+ for key, value in r.items():
+ if value.has_key(()):
+ if len(value) <> 1:
+ raise KeySpecError, \
+ "key definitions for %s clash"%(value.values(),)
+ else:
+ r[key] = value[()]
+ else:
+ r[key] = _compile_keymap(value)
+ return r
+
+def compile_keymap(keymap):
+ r = {}
+ for key, value in keymap:
+ k = parse_keys(key)
+ if value is None and r.has_key(k):
+ del r[k]
+ if k is not None:
+ r[k] = value
+ return _compile_keymap(r)
+
+def keyname(key):
+ longest_match = ''
+ longest_match_name = ''
+ for name, keyseq in keyset.items():
+ if keyseq and key.startswith(keyseq) and \
+ len(keyseq) > len(longest_match):
+ longest_match = keyseq
+ longest_match_name = name
+ if len(longest_match) > 0:
+ return longest_match_name, len(longest_match)
+ else:
+ return None, 0
+
+_unescapes = {'\r':'\\r', '\n':'\\n', '\177':'^?'}
+
+#for k,v in _escapes.items():
+# _unescapes[v] = k
+
+def unparse_key(keyseq):
+ if not keyseq:
+ return ''
+ name, s = keyname(keyseq)
+ if name:
+ if name <> 'escape' or s == len(keyseq):
+ return '\\<' + name + '>' + unparse_key(keyseq[s:])
+ else:
+ return '\\M-' + unparse_key(keyseq[1:])
+ else:
+ c = keyseq[0]
+ r = keyseq[1:]
+ if c == '\\':
+ p = '\\\\'
+ elif _unescapes.has_key(c):
+ p = _unescapes[c]
+ elif ord(c) < ord(' '):
+ p = '\\C-%s'%(chr(ord(c)+96),)
+ elif ord(' ') <= ord(c) <= ord('~'):
+ p = c
+ else:
+ p = '\\%03o'%(ord(c),)
+ return p + unparse_key(r)
+
+def _unparse_keyf(keyseq):
+ if not keyseq:
+ return []
+ name, s = keyname(keyseq)
+ if name:
+ if name <> 'escape' or s == len(keyseq):
+ return [name] + _unparse_keyf(keyseq[s:])
+ else:
+ rest = _unparse_keyf(keyseq[1:])
+ return ['M-'+rest[0]] + rest[1:]
+ else:
+ c = keyseq[0]
+ r = keyseq[1:]
+ if c == '\\':
+ p = '\\'
+ elif _unescapes.has_key(c):
+ p = _unescapes[c]
+ elif ord(c) < ord(' '):
+ p = 'C-%s'%(chr(ord(c)+96),)
+ elif ord(' ') <= ord(c) <= ord('~'):
+ p = c
+ else:
+ p = '\\%03o'%(ord(c),)
+ return [p] + _unparse_keyf(r)
+
+def unparse_keyf(keyseq):
+ return " ".join(_unparse_keyf(keyseq))
diff --git a/lib_pypy/pyrepl/python_reader.py b/lib_pypy/pyrepl/python_reader.py
new file mode 100644
index 0000000000..e34bacbe33
--- /dev/null
+++ b/lib_pypy/pyrepl/python_reader.py
@@ -0,0 +1,392 @@
+# Copyright 2000-2007 Michael Hudson-Doyle <micahel@gmail.com>
+# Bob Ippolito
+# Maciek Fijalkowski
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# one impressive collections of imports:
+from pyrepl.completing_reader import CompletingReader
+from pyrepl.historical_reader import HistoricalReader
+from pyrepl import completing_reader, reader
+from pyrepl import copy_code, commands, completer
+from pyrepl import module_lister
+import new, sys, os, re, code, traceback
+import atexit, warnings
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+try:
+ import imp
+ imp.find_module("twisted")
+ from twisted.internet import reactor
+ from twisted.internet.abstract import FileDescriptor
+except ImportError:
+ default_interactmethod = "interact"
+else:
+ default_interactmethod = "twistedinteract"
+
+CommandCompiler = code.CommandCompiler
+
+def eat_it(*args):
+ """this function eats warnings, if you were wondering"""
+ pass
+
+class maybe_accept(commands.Command):
+ def do(self):
+ r = self.reader
+ text = r.get_unicode()
+ try:
+ # ooh, look at the hack:
+ code = r.compiler("#coding:utf-8\n"+text.encode('utf-8'))
+ except (OverflowError, SyntaxError, ValueError):
+ self.finish = 1
+ else:
+ if code is None:
+ r.insert("\n")
+ else:
+ self.finish = 1
+
+from_line_prog = re.compile(
+ "^from\s+(?P<mod>[A-Za-z_.0-9]*)\s+import\s+(?P<name>[A-Za-z_.0-9]*)")
+import_line_prog = re.compile(
+ "^(?:import|from)\s+(?P<mod>[A-Za-z_.0-9]*)\s*$")
+
+def mk_saver(reader):
+ def saver(reader=reader):
+ try:
+ file = open(os.path.expanduser("~/.pythoni.hist"), "w")
+ except IOError:
+ pass
+ else:
+ pickle.dump(reader.history, file)
+ file.close()
+ return saver
+
+class PythonicReader(CompletingReader, HistoricalReader):
+ def collect_keymap(self):
+ return super(PythonicReader, self).collect_keymap() + (
+ (r'\n', 'maybe-accept'),
+ (r'\M-\n', 'insert-nl'))
+
+ def __init__(self, console, locals,
+ compiler=None):
+ super(PythonicReader, self).__init__(console)
+ self.completer = completer.Completer(locals)
+ st = self.syntax_table
+ for c in "._0123456789":
+ st[c] = reader.SYNTAX_WORD
+ self.locals = locals
+ if compiler is None:
+ self.compiler = CommandCompiler()
+ else:
+ self.compiler = compiler
+ try:
+ file = open(os.path.expanduser("~/.pythoni.hist"))
+ except IOError:
+ pass
+ else:
+ try:
+ self.history = pickle.load(file)
+ except:
+ self.history = []
+ self.historyi = len(self.history)
+ file.close()
+ atexit.register(mk_saver(self))
+ for c in [maybe_accept]:
+ self.commands[c.__name__] = c
+ self.commands[c.__name__.replace('_', '-')] = c
+
+ def get_completions(self, stem):
+ b = self.get_unicode()
+ m = import_line_prog.match(b)
+ if m:
+ if not self._module_list_ready:
+ module_lister._make_module_list()
+ self._module_list_ready = True
+
+ mod = m.group("mod")
+ try:
+ return module_lister.find_modules(mod)
+ except ImportError:
+ pass
+ m = from_line_prog.match(b)
+ if m:
+ mod, name = m.group("mod", "name")
+ try:
+ l = module_lister._packages[mod]
+ except KeyError:
+ try:
+ mod = __import__(mod, self.locals, self.locals, [''])
+ return [x for x in dir(mod) if x.startswith(name)]
+ except ImportError:
+ pass
+ else:
+ return [x[len(mod) + 1:]
+ for x in l if x.startswith(mod + '.' + name)]
+ try:
+ l = completing_reader.uniqify(self.completer.complete(stem))
+ return l
+ except (NameError, AttributeError):
+ return []
+
+class ReaderConsole(code.InteractiveInterpreter):
+ II_init = code.InteractiveInterpreter.__init__
+ def __init__(self, console, locals=None):
+ if locals is None:
+ locals = {}
+ self.II_init(locals)
+ self.compiler = CommandCompiler()
+ self.compile = self.compiler.compiler
+ self.reader = PythonicReader(console, locals, self.compiler)
+ locals['Reader'] = self.reader
+
+ def run_user_init_file(self):
+ for key in "PYREPLSTARTUP", "PYTHONSTARTUP":
+ initfile = os.environ.get(key)
+ if initfile is not None and os.path.exists(initfile):
+ break
+ else:
+ return
+ try:
+ execfile(initfile, self.locals, self.locals)
+ except:
+ etype, value, tb = sys.exc_info()
+ traceback.print_exception(etype, value, tb.tb_next)
+
+ def execute(self, text):
+ try:
+ # ooh, look at the hack:
+ code = self.compile("# coding:utf8\n"+text.encode('utf-8'),
+ '<input>', 'single')
+ except (OverflowError, SyntaxError, ValueError):
+ self.showsyntaxerror("<input>")
+ else:
+ self.runcode(code)
+ sys.stdout.flush()
+
+ def interact(self):
+ while 1:
+ try: # catches EOFError's and KeyboardInterrupts during execution
+ try: # catches KeyboardInterrupts during editing
+ try: # warning saver
+ # can't have warnings spewed onto terminal
+ sv = warnings.showwarning
+ warnings.showwarning = eat_it
+ l = unicode(self.reader.readline(), 'utf-8')
+ finally:
+ warnings.showwarning = sv
+ except KeyboardInterrupt:
+ print "KeyboardInterrupt"
+ else:
+ if l:
+ self.execute(l)
+ except EOFError:
+ break
+ except KeyboardInterrupt:
+ continue
+
+ def prepare(self):
+ self.sv_sw = warnings.showwarning
+ warnings.showwarning = eat_it
+ self.reader.prepare()
+ self.reader.refresh() # we want :after methods...
+
+ def restore(self):
+ self.reader.restore()
+ warnings.showwarning = self.sv_sw
+
+ def handle1(self, block=1):
+ try:
+ r = 1
+ r = self.reader.handle1(block)
+ except KeyboardInterrupt:
+ self.restore()
+ print "KeyboardInterrupt"
+ self.prepare()
+ else:
+ if self.reader.finished:
+ text = self.reader.get_unicode()
+ self.restore()
+ if text:
+ self.execute(text)
+ self.prepare()
+ return r
+
+ def tkfilehandler(self, file, mask):
+ try:
+ self.handle1(block=0)
+ except:
+ self.exc_info = sys.exc_info()
+
+ # how the <expletive> do you get this to work on Windows (without
+ # createfilehandler)? threads, I guess
+ def really_tkinteract(self):
+ import _tkinter
+ _tkinter.createfilehandler(
+ self.reader.console.input_fd, _tkinter.READABLE,
+ self.tkfilehandler)
+
+ self.exc_info = None
+ while 1:
+ # dooneevent will return 0 without blocking if there are
+ # no Tk windows, 1 after blocking until an event otherwise
+ # so the following does what we want (this wasn't expected
+ # to be obvious).
+ if not _tkinter.dooneevent(_tkinter.ALL_EVENTS):
+ self.handle1(block=1)
+ if self.exc_info:
+ type, value, tb = self.exc_info
+ self.exc_info = None
+ raise type, value, tb
+
+ def tkinteract(self):
+ """Run a Tk-aware Python interactive session.
+
+ This function simulates the Python top-level in a way that
+ allows Tk's mainloop to run."""
+
+ # attempting to understand the control flow of this function
+ # without help may cause internal injuries. so, some
+ # explanation.
+
+ # The outer while loop is there to restart the interaction if
+ # the user types control-c when execution is deep in our
+ # innards. I'm not sure this can't leave internals in an
+ # inconsistent state, but it's a good start.
+
+ # then the inside loop keeps calling self.handle1 until
+ # _tkinter gets imported; then control shifts to
+ # self.really_tkinteract, above.
+
+ # this function can only return via an exception; we mask
+ # EOFErrors (but they end the interaction) and
+ # KeyboardInterrupts cause a restart. All other exceptions
+ # are likely bugs in pyrepl (well, 'cept for SystemExit, of
+ # course).
+
+ while 1:
+ try:
+ try:
+ self.prepare()
+ try:
+ while 1:
+ if sys.modules.has_key("_tkinter"):
+ self.really_tkinteract()
+ # really_tkinteract is not expected to
+ # return except via an exception, but:
+ break
+ self.handle1()
+ except EOFError:
+ pass
+ finally:
+ self.restore()
+ except KeyboardInterrupt:
+ continue
+ else:
+ break
+
+ def twistedinteract(self):
+ from twisted.internet import reactor
+ from twisted.internet.abstract import FileDescriptor
+ import signal
+ outerself = self
+ class Me(FileDescriptor):
+ def fileno(self):
+ """ We want to select on FD 0 """
+ return 0
+
+ def doRead(self):
+ """called when input is ready"""
+ try:
+ outerself.handle1()
+ except EOFError:
+ reactor.stop()
+
+ reactor.addReader(Me())
+ reactor.callWhenRunning(signal.signal,
+ signal.SIGINT,
+ signal.default_int_handler)
+ self.prepare()
+ try:
+ reactor.run()
+ finally:
+ self.restore()
+
+
+ def cocoainteract(self, inputfilehandle=None, outputfilehandle=None):
+ # only call this when there's a run loop already going!
+ # note that unlike the other *interact methods, this returns immediately
+ from cocoasupport import CocoaInteracter
+ self.cocoainteracter = CocoaInteracter.alloc().init(self, inputfilehandle, outputfilehandle)
+
+
+def main(use_pygame_console=0, interactmethod=default_interactmethod, print_banner=True, clear_main=True):
+ si, se, so = sys.stdin, sys.stderr, sys.stdout
+ try:
+ if 0 and use_pygame_console: # pygame currently borked
+ from pyrepl.pygame_console import PyGameConsole, FakeStdin, FakeStdout
+ con = PyGameConsole()
+ sys.stderr = sys.stdout = FakeStdout(con)
+ sys.stdin = FakeStdin(con)
+ else:
+ from pyrepl.unix_console import UnixConsole
+ try:
+ import locale
+ except ImportError:
+ encoding = None
+ else:
+ if hasattr(locale, 'nl_langinfo') \
+ and hasattr(locale, 'CODESET'):
+ encoding = locale.nl_langinfo(locale.CODESET)
+ elif os.environ.get('TERM_PROGRAM') == 'Apple_Terminal':
+ # /me whistles innocently...
+ code = int(os.popen(
+ "defaults read com.apple.Terminal StringEncoding"
+ ).read())
+ if code == 4:
+ encoding = 'utf-8'
+ # More could go here -- and what's here isn't
+ # bulletproof. What would be? AppleScript?
+ # Doesn't seem to be possible.
+ else:
+ encoding = None
+ else:
+ encoding = None # so you get ASCII...
+ con = UnixConsole(0, 1, None, encoding)
+ if print_banner:
+ print "Python", sys.version, "on", sys.platform
+ print 'Type "help", "copyright", "credits" or "license" '\
+ 'for more information.'
+ sys.path.insert(0, os.getcwd())
+
+ if clear_main and __name__ != '__main__':
+ mainmod = new.module('__main__')
+ sys.modules['__main__'] = mainmod
+ else:
+ mainmod = sys.modules['__main__']
+
+ rc = ReaderConsole(con, mainmod.__dict__)
+ rc.reader._module_list_ready = False
+ rc.run_user_init_file()
+ getattr(rc, interactmethod)()
+ finally:
+ sys.stdin, sys.stderr, sys.stdout = si, se, so
+
+if __name__ == '__main__':
+ main()
diff --git a/lib_pypy/pyrepl/reader.py b/lib_pypy/pyrepl/reader.py
new file mode 100644
index 0000000000..653a6f1801
--- /dev/null
+++ b/lib_pypy/pyrepl/reader.py
@@ -0,0 +1,585 @@
+# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
+# Antonio Cuni
+# Armin Rigo
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import types
+from pyrepl import unicodedata_
+from pyrepl import commands
+from pyrepl import input
+
+def _make_unctrl_map():
+ uc_map = {}
+ for c in map(unichr, range(256)):
+ if unicodedata_.category(c)[0] <> 'C':
+ uc_map[c] = c
+ for i in range(32):
+ c = unichr(i)
+ uc_map[c] = u'^' + unichr(ord('A') + i - 1)
+ uc_map['\t'] = ' ' # display TABs as 4 characters
+ uc_map['\177'] = u'^?'
+ for i in range(256):
+ c = unichr(i)
+ if not uc_map.has_key(c):
+ uc_map[c] = u'\\%03o'%i
+ return uc_map
+
+# disp_str proved to be a bottleneck for large inputs, so it's been
+# rewritten in C; it's not required though.
+try:
+ raise ImportError # currently it's borked by the unicode support
+
+ from _pyrepl_utils import disp_str, init_unctrl_map
+
+ init_unctrl_map(_make_unctrl_map())
+
+ del init_unctrl_map
+except ImportError:
+ def _my_unctrl(c, u=_make_unctrl_map()):
+ if c in u:
+ return u[c]
+ else:
+ if unicodedata_.category(c).startswith('C'):
+ return '\u%04x'%(ord(c),)
+ else:
+ return c
+
+ def disp_str(buffer, join=''.join, uc=_my_unctrl):
+ """ disp_str(buffer:string) -> (string, [int])
+
+ Return the string that should be the printed represenation of
+ |buffer| and a list detailing where the characters of |buffer|
+ get used up. E.g.:
+
+ >>> disp_str(chr(3))
+ ('^C', [1, 0])
+
+ the list always contains 0s or 1s at present; it could conceivably
+ go higher as and when unicode support happens."""
+ s = map(uc, buffer)
+ return (join(s),
+ map(ord, join(map(lambda x:'\001'+(len(x)-1)*'\000', s))))
+
+ del _my_unctrl
+
+del _make_unctrl_map
+
+# syntax classes:
+
+[SYNTAX_WHITESPACE,
+ SYNTAX_WORD,
+ SYNTAX_SYMBOL] = range(3)
+
+def make_default_syntax_table():
+ # XXX perhaps should use some unicodedata here?
+ st = {}
+ for c in map(unichr, range(256)):
+ st[c] = SYNTAX_SYMBOL
+ for c in [a for a in map(unichr, range(256)) if a.isalpha()]:
+ st[c] = SYNTAX_WORD
+ st[u'\n'] = st[u' '] = SYNTAX_WHITESPACE
+ return st
+
+default_keymap = tuple(
+ [(r'\C-a', 'beginning-of-line'),
+ (r'\C-b', 'left'),
+ (r'\C-c', 'interrupt'),
+ (r'\C-d', 'delete'),
+ (r'\C-e', 'end-of-line'),
+ (r'\C-f', 'right'),
+ (r'\C-g', 'cancel'),
+ (r'\C-h', 'backspace'),
+ (r'\C-j', 'accept'),
+ (r'\<return>', 'accept'),
+ (r'\C-k', 'kill-line'),
+ (r'\C-l', 'clear-screen'),
+ (r'\C-m', 'accept'),
+ (r'\C-q', 'quoted-insert'),
+ (r'\C-t', 'transpose-characters'),
+ (r'\C-u', 'unix-line-discard'),
+ (r'\C-v', 'quoted-insert'),
+ (r'\C-w', 'unix-word-rubout'),
+ (r'\C-x\C-u', 'upcase-region'),
+ (r'\C-y', 'yank'),
+ (r'\C-z', 'suspend'),
+
+ (r'\M-b', 'backward-word'),
+ (r'\M-c', 'capitalize-word'),
+ (r'\M-d', 'kill-word'),
+ (r'\M-f', 'forward-word'),
+ (r'\M-l', 'downcase-word'),
+ (r'\M-t', 'transpose-words'),
+ (r'\M-u', 'upcase-word'),
+ (r'\M-y', 'yank-pop'),
+ (r'\M--', 'digit-arg'),
+ (r'\M-0', 'digit-arg'),
+ (r'\M-1', 'digit-arg'),
+ (r'\M-2', 'digit-arg'),
+ (r'\M-3', 'digit-arg'),
+ (r'\M-4', 'digit-arg'),
+ (r'\M-5', 'digit-arg'),
+ (r'\M-6', 'digit-arg'),
+ (r'\M-7', 'digit-arg'),
+ (r'\M-8', 'digit-arg'),
+ (r'\M-9', 'digit-arg'),
+ #(r'\M-\n', 'insert-nl'),
+ ('\\\\', 'self-insert')] + \
+ [(c, 'self-insert')
+ for c in map(chr, range(32, 127)) if c <> '\\'] + \
+ [(c, 'self-insert')
+ for c in map(chr, range(128, 256)) if c.isalpha()] + \
+ [(r'\<up>', 'up'),
+ (r'\<down>', 'down'),
+ (r'\<left>', 'left'),
+ (r'\<right>', 'right'),
+ (r'\<insert>', 'quoted-insert'),
+ (r'\<delete>', 'delete'),
+ (r'\<backspace>', 'backspace'),
+ (r'\M-\<backspace>', 'backward-kill-word'),
+ (r'\<end>', 'end'),
+ (r'\<home>', 'home'),
+ (r'\<f1>', 'help'),
+ (r'\EOF', 'end'), # the entries in the terminfo database for xterms
+ (r'\EOH', 'home'), # seem to be wrong. this is a less than ideal
+ # workaround
+ ])
+
+del c # from the listcomps
+
+class Reader(object):
+ """The Reader class implements the bare bones of a command reader,
+ handling such details as editing and cursor motion. What it does
+ not support are such things as completion or history support -
+ these are implemented elsewhere.
+
+ Instance variables of note include:
+
+ * buffer:
+ A *list* (*not* a string at the moment :-) containing all the
+ characters that have been entered.
+ * console:
+ Hopefully encapsulates the OS dependent stuff.
+ * pos:
+ A 0-based index into `buffer' for where the insertion point
+ is.
+ * screeninfo:
+ Ahem. This list contains some info needed to move the
+ insertion point around reasonably efficiently. I'd like to
+ get rid of it, because its contents are obtuse (to put it
+ mildly) but I haven't worked out if that is possible yet.
+ * cxy, lxy:
+ the position of the insertion point in screen ... XXX
+ * syntax_table:
+ Dictionary mapping characters to `syntax class'; read the
+ emacs docs to see what this means :-)
+ * commands:
+ Dictionary mapping command names to command classes.
+ * arg:
+ The emacs-style prefix argument. It will be None if no such
+ argument has been provided.
+ * dirty:
+ True if we need to refresh the display.
+ * kill_ring:
+ The emacs-style kill-ring; manipulated with yank & yank-pop
+ * ps1, ps2, ps3, ps4:
+ prompts. ps1 is the prompt for a one-line input; for a
+ multiline input it looks like:
+ ps2> first line of input goes here
+ ps3> second and further
+ ps3> lines get ps3
+ ...
+ ps4> and the last one gets ps4
+ As with the usual top-level, you can set these to instances if
+ you like; str() will be called on them (once) at the beginning
+ of each command. Don't put really long or newline containing
+ strings here, please!
+ This is just the default policy; you can change it freely by
+ overriding get_prompt() (and indeed some standard subclasses
+ do).
+ * finished:
+ handle1 will set this to a true value if a command signals
+ that we're done.
+ """
+
+ help_text = """\
+This is pyrepl. Hear my roar.
+
+Helpful text may appear here at some point in the future when I'm
+feeling more loquacious than I am now."""
+
+ msg_at_bottom = True
+
+ def __init__(self, console):
+ self.buffer = []
+ self.ps1 = "->> "
+ self.ps2 = "/>> "
+ self.ps3 = "|.. "
+ self.ps4 = "\__ "
+ self.kill_ring = []
+ self.arg = None
+ self.finished = 0
+ self.console = console
+ self.commands = {}
+ self.msg = ''
+ for v in vars(commands).values():
+ if ( isinstance(v, type)
+ and issubclass(v, commands.Command)
+ and v.__name__[0].islower() ):
+ self.commands[v.__name__] = v
+ self.commands[v.__name__.replace('_', '-')] = v
+ self.syntax_table = make_default_syntax_table()
+ self.input_trans_stack = []
+ self.keymap = self.collect_keymap()
+ self.input_trans = input.KeymapTranslator(
+ self.keymap,
+ invalid_cls='invalid-key',
+ character_cls='self-insert')
+
+ def collect_keymap(self):
+ return default_keymap
+
+ def calc_screen(self):
+ """The purpose of this method is to translate changes in
+ self.buffer into changes in self.screen. Currently it rips
+ everything down and starts from scratch, which whilst not
+ especially efficient is certainly simple(r).
+ """
+ lines = self.get_unicode().split("\n")
+ screen = []
+ screeninfo = []
+ w = self.console.width - 1
+ p = self.pos
+ for ln, line in zip(range(len(lines)), lines):
+ ll = len(line)
+ if 0 <= p <= ll:
+ if self.msg and not self.msg_at_bottom:
+ for mline in self.msg.split("\n"):
+ screen.append(mline)
+ screeninfo.append((0, []))
+ self.lxy = p, ln
+ prompt = self.get_prompt(ln, ll >= p >= 0)
+ p -= ll + 1
+ lp = len(prompt)
+ l, l2 = disp_str(line)
+ wrapcount = (len(l) + lp) / w
+ if wrapcount == 0:
+ screen.append(prompt + l)
+ screeninfo.append((lp, l2+[1]))
+ else:
+ screen.append(prompt + l[:w-lp] + "\\")
+ screeninfo.append((lp, l2[:w-lp]))
+ for i in range(-lp + w, -lp + wrapcount*w, w):
+ screen.append(l[i:i+w] + "\\")
+ screeninfo.append((0, l2[i:i + w]))
+ screen.append(l[wrapcount*w - lp:])
+ screeninfo.append((0, l2[wrapcount*w - lp:]+[1]))
+ self.screeninfo = screeninfo
+ self.cxy = self.pos2xy(self.pos)
+ if self.msg and self.msg_at_bottom:
+ for mline in self.msg.split("\n"):
+ screen.append(mline)
+ screeninfo.append((0, []))
+ return screen
+
+ def bow(self, p=None):
+ """Return the 0-based index of the word break preceding p most
+ immediately.
+
+ p defaults to self.pos; word boundaries are determined using
+ self.syntax_table."""
+ if p is None:
+ p = self.pos
+ st = self.syntax_table
+ b = self.buffer
+ p -= 1
+ while p >= 0 and st.get(b[p], SYNTAX_WORD) <> SYNTAX_WORD:
+ p -= 1
+ while p >= 0 and st.get(b[p], SYNTAX_WORD) == SYNTAX_WORD:
+ p -= 1
+ return p + 1
+
+ def eow(self, p=None):
+ """Return the 0-based index of the word break following p most
+ immediately.
+
+ p defaults to self.pos; word boundaries are determined using
+ self.syntax_table."""
+ if p is None:
+ p = self.pos
+ st = self.syntax_table
+ b = self.buffer
+ while p < len(b) and st.get(b[p], SYNTAX_WORD) <> SYNTAX_WORD:
+ p += 1
+ while p < len(b) and st.get(b[p], SYNTAX_WORD) == SYNTAX_WORD:
+ p += 1
+ return p
+
+ def bol(self, p=None):
+ """Return the 0-based index of the line break preceding p most
+ immediately.
+
+ p defaults to self.pos."""
+ # XXX there are problems here.
+ if p is None:
+ p = self.pos
+ b = self.buffer
+ p -= 1
+ while p >= 0 and b[p] <> '\n':
+ p -= 1
+ return p + 1
+
+ def eol(self, p=None):
+ """Return the 0-based index of the line break following p most
+ immediately.
+
+ p defaults to self.pos."""
+ if p is None:
+ p = self.pos
+ b = self.buffer
+ while p < len(b) and b[p] <> '\n':
+ p += 1
+ return p
+
+ def get_arg(self, default=1):
+ """Return any prefix argument that the user has supplied,
+ returning `default' if there is None. `default' defaults
+ (groan) to 1."""
+ if self.arg is None:
+ return default
+ else:
+ return self.arg
+
+ def get_prompt(self, lineno, cursor_on_line):
+ """Return what should be in the left-hand margin for line
+ `lineno'."""
+ if self.arg is not None and cursor_on_line:
+ return "(arg: %s) "%self.arg
+ if "\n" in self.buffer:
+ if lineno == 0:
+ return self._ps2
+ elif lineno == self.buffer.count("\n"):
+ return self._ps4
+ else:
+ return self._ps3
+ else:
+ return self._ps1
+
+ def push_input_trans(self, itrans):
+ self.input_trans_stack.append(self.input_trans)
+ self.input_trans = itrans
+
+ def pop_input_trans(self):
+ self.input_trans = self.input_trans_stack.pop()
+
+ def pos2xy(self, pos):
+ """Return the x, y coordinates of position 'pos'."""
+ # this *is* incomprehensible, yes.
+ y = 0
+ assert 0 <= pos <= len(self.buffer)
+ if pos == len(self.buffer):
+ y = len(self.screeninfo) - 1
+ p, l2 = self.screeninfo[y]
+ return p + len(l2) - 1, y
+ else:
+ for p, l2 in self.screeninfo:
+ l = l2.count(1)
+ if l > pos:
+ break
+ else:
+ pos -= l
+ y += 1
+ c = 0
+ i = 0
+ while c < pos:
+ c += l2[i]
+ i += 1
+ while l2[i] == 0:
+ i += 1
+ return p + i, y
+
+ def insert(self, text):
+ """Insert 'text' at the insertion point."""
+ self.buffer[self.pos:self.pos] = list(text)
+ self.pos += len(text)
+ self.dirty = 1
+
+ def update_cursor(self):
+ """Move the cursor to reflect changes in self.pos"""
+ self.cxy = self.pos2xy(self.pos)
+ self.console.move_cursor(*self.cxy)
+
+ def after_command(self, cmd):
+ """This function is called to allow post command cleanup."""
+ if getattr(cmd, "kills_digit_arg", 1):
+ if self.arg is not None:
+ self.dirty = 1
+ self.arg = None
+
+ def prepare(self):
+ """Get ready to run. Call restore when finished. You must not
+ write to the console in between the calls to prepare and
+ restore."""
+ try:
+ self.console.prepare()
+ self.arg = None
+ self.screeninfo = []
+ self.finished = 0
+ del self.buffer[:]
+ self.pos = 0
+ self.dirty = 1
+ self.last_command = None
+ self._ps1, self._ps2, self._ps3, self._ps4 = \
+ map(str, [self.ps1, self.ps2, self.ps3, self.ps4])
+ except:
+ self.restore()
+ raise
+
+ def last_command_is(self, klass):
+ if not self.last_command:
+ return 0
+ return issubclass(klass, self.last_command)
+
+ def restore(self):
+ """Clean up after a run."""
+ self.console.restore()
+
+ def finish(self):
+ """Called when a command signals that we're finished."""
+ pass
+
+ def error(self, msg="none"):
+ self.msg = "! " + msg + " "
+ self.dirty = 1
+ self.console.beep()
+
+ def update_screen(self):
+ if self.dirty:
+ self.refresh()
+
+ def refresh(self):
+ """Recalculate and refresh the screen."""
+ # this call sets up self.cxy, so call it first.
+ screen = self.calc_screen()
+ self.console.refresh(screen, self.cxy)
+ self.dirty = 0 # forgot this for a while (blush)
+
+ def do_cmd(self, cmd):
+ #print cmd
+ if isinstance(cmd[0], str):
+ cmd = self.commands.get(cmd[0],
+ commands.invalid_command)(self, cmd)
+ elif isinstance(cmd[0], type):
+ cmd = cmd[0](self, cmd)
+
+ cmd.do()
+
+ self.after_command(cmd)
+
+ if self.dirty:
+ self.refresh()
+ else:
+ self.update_cursor()
+
+ if not isinstance(cmd, commands.digit_arg):
+ self.last_command = cmd.__class__
+
+ self.finished = cmd.finish
+ if self.finished:
+ self.console.finish()
+ self.finish()
+
+ def handle1(self, block=1):
+ """Handle a single event. Wait as long as it takes if block
+ is true (the default), otherwise return None if no event is
+ pending."""
+
+ if self.msg:
+ self.msg = ''
+ self.dirty = 1
+
+ while 1:
+ event = self.console.get_event(block)
+ if not event: # can only happen if we're not blocking
+ return None
+
+ if event.evt == 'key':
+ self.input_trans.push(event)
+ elif event.evt == 'scroll':
+ self.refresh()
+ elif event.evt == 'resize':
+ self.refresh()
+ else:
+ pass
+
+ cmd = self.input_trans.get()
+
+ if cmd is None:
+ if block:
+ continue
+ else:
+ return None
+
+ self.do_cmd(cmd)
+ return 1
+
+ def push_char(self, char):
+ self.console.push_char(char)
+ self.handle1(0)
+
+ def readline(self):
+ """Read a line. The implementation of this method also shows
+ how to drive Reader if you want more control over the event
+ loop."""
+ self.prepare()
+ try:
+ self.refresh()
+ while not self.finished:
+ self.handle1()
+ return self.get_buffer()
+ finally:
+ self.restore()
+
+ def bind(self, spec, command):
+ self.keymap = self.keymap + ((spec, command),)
+ self.input_trans = input.KeymapTranslator(
+ self.keymap,
+ invalid_cls='invalid-key',
+ character_cls='self-insert')
+
+ def get_buffer(self, encoding=None):
+ if encoding is None:
+ encoding = self.console.encoding
+ return u''.join(self.buffer).encode(self.console.encoding)
+
+ def get_unicode(self):
+ """Return the current buffer as a unicode string."""
+ return u''.join(self.buffer)
+
+def test():
+ from pyrepl.unix_console import UnixConsole
+ reader = Reader(UnixConsole())
+ reader.ps1 = "**> "
+ reader.ps2 = "/*> "
+ reader.ps3 = "|*> "
+ reader.ps4 = "\*> "
+ while reader.readline():
+ pass
+
+if __name__=='__main__':
+ test()
diff --git a/lib_pypy/pyrepl/readline.py b/lib_pypy/pyrepl/readline.py
new file mode 100644
index 0000000000..09ae92b1f1
--- /dev/null
+++ b/lib_pypy/pyrepl/readline.py
@@ -0,0 +1,404 @@
+# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
+# Alex Gaynor
+# Antonio Cuni
+# Armin Rigo
+# Holger Krekel
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""A compatibility wrapper reimplementing the 'readline' standard module
+on top of pyrepl. Not all functionalities are supported. Contains
+extensions for multiline input.
+"""
+
+import sys, os
+from pyrepl import commands
+from pyrepl.historical_reader import HistoricalReader
+from pyrepl.completing_reader import CompletingReader
+from pyrepl.unix_console import UnixConsole, _error
+
+
+ENCODING = 'latin1' # XXX hard-coded
+
+__all__ = ['add_history',
+ 'clear_history',
+ 'get_begidx',
+ 'get_completer',
+ 'get_completer_delims',
+ 'get_current_history_length',
+ 'get_endidx',
+ 'get_history_item',
+ 'get_history_length',
+ 'get_line_buffer',
+ 'insert_text',
+ 'parse_and_bind',
+ 'read_history_file',
+ 'read_init_file',
+ 'redisplay',
+ 'remove_history_item',
+ 'replace_history_item',
+ 'set_completer',
+ 'set_completer_delims',
+ 'set_history_length',
+ 'set_pre_input_hook',
+ 'set_startup_hook',
+ 'write_history_file',
+ # ---- multiline extensions ----
+ 'multiline_input',
+ ]
+
+# ____________________________________________________________
+
+class ReadlineConfig(object):
+ readline_completer = None
+ completer_delims = dict.fromkeys(' \t\n`~!@#$%^&*()-=+[{]}\\|;:\'",<>/?')
+
+class ReadlineAlikeReader(HistoricalReader, CompletingReader):
+
+ assume_immutable_completions = False
+ use_brackets = False
+ sort_in_column = True
+
+ def error(self, msg="none"):
+ pass # don't show error messages by default
+
+ def get_stem(self):
+ b = self.buffer
+ p = self.pos - 1
+ completer_delims = self.config.completer_delims
+ while p >= 0 and b[p] not in completer_delims:
+ p -= 1
+ return ''.join(b[p+1:self.pos])
+
+ def get_completions(self, stem):
+ result = []
+ function = self.config.readline_completer
+ if function is not None:
+ try:
+ stem = str(stem) # rlcompleter.py seems to not like unicode
+ except UnicodeEncodeError:
+ pass # but feed unicode anyway if we have no choice
+ state = 0
+ while True:
+ try:
+ next = function(stem, state)
+ except:
+ break
+ if not isinstance(next, str):
+ break
+ result.append(next)
+ state += 1
+ # emulate the behavior of the standard readline that sorts
+ # the completions before displaying them.
+ result.sort()
+ return result
+
+ def get_trimmed_history(self, maxlength):
+ if maxlength >= 0:
+ cut = len(self.history) - maxlength
+ if cut < 0:
+ cut = 0
+ else:
+ cut = 0
+ return self.history[cut:]
+
+ # --- simplified support for reading multiline Python statements ---
+
+ # This duplicates small parts of pyrepl.python_reader. I'm not
+ # reusing the PythonicReader class directly for two reasons. One is
+ # to try to keep as close as possible to CPython's prompt. The
+ # other is that it is the readline module that we are ultimately
+ # implementing here, and I don't want the built-in raw_input() to
+ # start trying to read multiline inputs just because what the user
+ # typed look like valid but incomplete Python code. So we get the
+ # multiline feature only when using the multiline_input() function
+ # directly (see _pypy_interact.py).
+
+ more_lines = None
+
+ def collect_keymap(self):
+ return super(ReadlineAlikeReader, self).collect_keymap() + (
+ (r'\n', 'maybe-accept'),)
+
+ def __init__(self, console):
+ super(ReadlineAlikeReader, self).__init__(console)
+ self.commands['maybe_accept'] = maybe_accept
+ self.commands['maybe-accept'] = maybe_accept
+
+ def after_command(self, cmd):
+ super(ReadlineAlikeReader, self).after_command(cmd)
+ if self.more_lines is None:
+ # Force single-line input if we are in raw_input() mode.
+ # Although there is no direct way to add a \n in this mode,
+ # multiline buffers can still show up using various
+ # commands, e.g. navigating the history.
+ try:
+ index = self.buffer.index("\n")
+ except ValueError:
+ pass
+ else:
+ self.buffer = self.buffer[:index]
+ if self.pos > len(self.buffer):
+ self.pos = len(self.buffer)
+
+class maybe_accept(commands.Command):
+ def do(self):
+ r = self.reader
+ r.dirty = 1 # this is needed to hide the completion menu, if visible
+ #
+ # if there are already several lines and the cursor
+ # is not on the last one, always insert a new \n.
+ text = r.get_unicode()
+ if "\n" in r.buffer[r.pos:]:
+ r.insert("\n")
+ elif r.more_lines is not None and r.more_lines(text):
+ r.insert("\n")
+ else:
+ self.finish = 1
+
+# ____________________________________________________________
+
+class _ReadlineWrapper(object):
+ f_in = 0
+ f_out = 1
+ reader = None
+ saved_history_length = -1
+ startup_hook = None
+ config = ReadlineConfig()
+
+ def get_reader(self):
+ if self.reader is None:
+ console = UnixConsole(self.f_in, self.f_out, encoding=ENCODING)
+ self.reader = ReadlineAlikeReader(console)
+ self.reader.config = self.config
+ return self.reader
+
+ def raw_input(self, prompt=''):
+ try:
+ reader = self.get_reader()
+ except _error:
+ return _old_raw_input(prompt)
+ if self.startup_hook is not None:
+ self.startup_hook()
+ reader.ps1 = prompt
+ return reader.readline()
+
+ def multiline_input(self, more_lines, ps1, ps2):
+ """Read an input on possibly multiple lines, asking for more
+ lines as long as 'more_lines(unicodetext)' returns an object whose
+ boolean value is true.
+ """
+ reader = self.get_reader()
+ saved = reader.more_lines
+ try:
+ reader.more_lines = more_lines
+ reader.ps1 = reader.ps2 = ps1
+ reader.ps3 = reader.ps4 = ps2
+ return reader.readline()
+ finally:
+ reader.more_lines = saved
+
+ def parse_and_bind(self, string):
+ pass # XXX we don't support parsing GNU-readline-style init files
+
+ def set_completer(self, function=None):
+ self.config.readline_completer = function
+
+ def get_completer(self):
+ return self.config.readline_completer
+
+ def set_completer_delims(self, string):
+ self.config.completer_delims = dict.fromkeys(string)
+
+ def get_completer_delims(self):
+ chars = self.config.completer_delims.keys()
+ chars.sort()
+ return ''.join(chars)
+
+ def _histline(self, line):
+ return unicode(line.rstrip('\n'), ENCODING)
+
+ def get_history_length(self):
+ return self.saved_history_length
+
+ def set_history_length(self, length):
+ self.saved_history_length = length
+
+ def get_current_history_length(self):
+ return len(self.get_reader().history)
+
+ def read_history_file(self, filename='~/.history'):
+ # multiline extension (really a hack) for the end of lines that
+ # are actually continuations inside a single multiline_input()
+ # history item: we use \r\n instead of just \n. If the history
+ # file is passed to GNU readline, the extra \r are just ignored.
+ history = self.get_reader().history
+ f = open(os.path.expanduser(filename), 'r')
+ buffer = []
+ for line in f:
+ if line.endswith('\r\n'):
+ buffer.append(line)
+ else:
+ line = self._histline(line)
+ if buffer:
+ line = ''.join(buffer).replace('\r', '') + line
+ del buffer[:]
+ if line:
+ history.append(line)
+ f.close()
+
+ def write_history_file(self, filename='~/.history'):
+ maxlength = self.saved_history_length
+ history = self.get_reader().get_trimmed_history(maxlength)
+ f = open(os.path.expanduser(filename), 'w')
+ for entry in history:
+ if isinstance(entry, unicode):
+ entry = entry.encode(ENCODING)
+ entry = entry.replace('\n', '\r\n') # multiline history support
+ f.write(entry + '\n')
+ f.close()
+
+ def clear_history(self):
+ del self.get_reader().history[:]
+
+ def get_history_item(self, index):
+ history = self.get_reader().history
+ if 1 <= index <= len(history):
+ return history[index-1]
+ else:
+ return None # blame readline.c for not raising
+
+ def remove_history_item(self, index):
+ history = self.get_reader().history
+ if 0 <= index < len(history):
+ del history[index]
+ else:
+ raise ValueError("No history item at position %d" % index)
+ # blame readline.c for raising ValueError
+
+ def replace_history_item(self, index, line):
+ history = self.get_reader().history
+ if 0 <= index < len(history):
+ history[index] = self._histline(line)
+ else:
+ raise ValueError("No history item at position %d" % index)
+ # blame readline.c for raising ValueError
+
+ def add_history(self, line):
+ self.get_reader().history.append(self._histline(line))
+
+ def set_startup_hook(self, function=None):
+ self.startup_hook = function
+
+ def get_line_buffer(self):
+ return self.get_reader().get_buffer()
+
+ def _get_idxs(self):
+ start = cursor = self.get_reader().pos
+ buf = self.get_line_buffer()
+ for i in xrange(cursor - 1, -1, -1):
+ if buf[i] in self.get_completer_delims():
+ break
+ start = i
+ return start, cursor
+
+ def get_begidx(self):
+ return self._get_idxs()[0]
+
+ def get_endidx(self):
+ return self._get_idxs()[1]
+
+ def insert_text(self, text):
+ return self.get_reader().insert(text)
+
+
+_wrapper = _ReadlineWrapper()
+
+# ____________________________________________________________
+# Public API
+
+parse_and_bind = _wrapper.parse_and_bind
+set_completer = _wrapper.set_completer
+get_completer = _wrapper.get_completer
+set_completer_delims = _wrapper.set_completer_delims
+get_completer_delims = _wrapper.get_completer_delims
+get_history_length = _wrapper.get_history_length
+set_history_length = _wrapper.set_history_length
+get_current_history_length = _wrapper.get_current_history_length
+read_history_file = _wrapper.read_history_file
+write_history_file = _wrapper.write_history_file
+clear_history = _wrapper.clear_history
+get_history_item = _wrapper.get_history_item
+remove_history_item = _wrapper.remove_history_item
+replace_history_item = _wrapper.replace_history_item
+add_history = _wrapper.add_history
+set_startup_hook = _wrapper.set_startup_hook
+get_line_buffer = _wrapper.get_line_buffer
+get_begidx = _wrapper.get_begidx
+get_endidx = _wrapper.get_endidx
+insert_text = _wrapper.insert_text
+
+# Extension
+multiline_input = _wrapper.multiline_input
+
+# Internal hook
+_get_reader = _wrapper.get_reader
+
+# ____________________________________________________________
+# Stubs
+
+def _make_stub(_name, _ret):
+ def stub(*args, **kwds):
+ import warnings
+ warnings.warn("readline.%s() not implemented" % _name, stacklevel=2)
+ stub.func_name = _name
+ globals()[_name] = stub
+
+for _name, _ret in [
+ ('read_init_file', None),
+ ('redisplay', None),
+ ('set_pre_input_hook', None),
+ ]:
+ assert _name not in globals(), _name
+ _make_stub(_name, _ret)
+
+# ____________________________________________________________
+
+def _setup():
+ global _old_raw_input
+ try:
+ f_in = sys.stdin.fileno()
+ f_out = sys.stdout.fileno()
+ except (AttributeError, ValueError):
+ return
+ if not os.isatty(f_in) or not os.isatty(f_out):
+ return
+
+ _wrapper.f_in = f_in
+ _wrapper.f_out = f_out
+
+ if hasattr(sys, '__raw_input__'): # PyPy
+ _old_raw_input = sys.__raw_input__
+ sys.__raw_input__ = _wrapper.raw_input
+ else:
+ # this is not really what readline.c does. Better than nothing I guess
+ import __builtin__
+ _old_raw_input = __builtin__.raw_input
+ __builtin__.raw_input = _wrapper.raw_input
+
+_setup()
diff --git a/lib_pypy/pyrepl/simple_interact.py b/lib_pypy/pyrepl/simple_interact.py
new file mode 100644
index 0000000000..0937582857
--- /dev/null
+++ b/lib_pypy/pyrepl/simple_interact.py
@@ -0,0 +1,64 @@
+# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
+# Armin Rigo
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""This is an alternative to python_reader which tries to emulate
+the CPython prompt as closely as possible, with the exception of
+allowing multiline input and multiline history entries.
+"""
+
+import sys
+from pyrepl.readline import multiline_input, _error, _get_reader
+
+def check(): # returns False if there is a problem initializing the state
+ try:
+ _get_reader()
+ except _error:
+ return False
+ return True
+
+def run_multiline_interactive_console(mainmodule=None):
+ import code
+ if mainmodule is None:
+ import __main__ as mainmodule
+ console = code.InteractiveConsole(mainmodule.__dict__)
+
+ def more_lines(unicodetext):
+ # ooh, look at the hack:
+ src = "#coding:utf-8\n"+unicodetext.encode('utf-8')
+ try:
+ code = console.compile(src, '<input>', 'single')
+ except (OverflowError, SyntaxError, ValueError):
+ return False
+ else:
+ return code is None
+
+ while 1:
+ try:
+ ps1 = getattr(sys, 'ps1', '>>> ')
+ ps2 = getattr(sys, 'ps2', '... ')
+ try:
+ statement = multiline_input(more_lines, ps1, ps2)
+ except EOFError:
+ break
+ more = console.push(statement)
+ assert not more
+ except KeyboardInterrupt:
+ console.write("\nKeyboardInterrupt\n")
+ console.resetbuffer()
diff --git a/lib_pypy/pyrepl/test/test_functional.py b/lib_pypy/pyrepl/test/test_functional.py
new file mode 100644
index 0000000000..13e551d998
--- /dev/null
+++ b/lib_pypy/pyrepl/test/test_functional.py
@@ -0,0 +1,50 @@
+# Copyright 2000-2007 Michael Hudson-Doyle <micahel@gmail.com>
+# Maciek Fijalkowski
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# some functional tests, to see if this is really working
+
+import py
+import sys
+
+class TestTerminal(object):
+ def _spawn(self, *args, **kwds):
+ try:
+ import pexpect
+ except ImportError, e:
+ py.test.skip(str(e))
+ kwds.setdefault('timeout', 10)
+ child = pexpect.spawn(*args, **kwds)
+ child.logfile = sys.stdout
+ return child
+
+ def spawn(self, argv=[]):
+ # avoid running start.py, cause it might contain
+ # things like readline or rlcompleter(2) included
+ child = self._spawn(sys.executable, ['-S'] + argv)
+ child.sendline('from pyrepl.python_reader import main')
+ child.sendline('main()')
+ return child
+
+ def test_basic(self):
+ child = self.spawn()
+ child.sendline('a = 3')
+ child.sendline('a')
+ child.expect('3')
+
diff --git a/lib_pypy/pyrepl/tests/__init__.py b/lib_pypy/pyrepl/tests/__init__.py
new file mode 100644
index 0000000000..36b58bb57c
--- /dev/null
+++ b/lib_pypy/pyrepl/tests/__init__.py
@@ -0,0 +1,20 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# moo
diff --git a/lib_pypy/pyrepl/tests/basic.py b/lib_pypy/pyrepl/tests/basic.py
new file mode 100644
index 0000000000..14badd5b2b
--- /dev/null
+++ b/lib_pypy/pyrepl/tests/basic.py
@@ -0,0 +1,115 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from pyrepl.console import Event
+from pyrepl.tests.infrastructure import ReaderTestCase, EA, run_testcase
+
+class SimpleTestCase(ReaderTestCase):
+
+ def test_basic(self):
+ self.run_test([(('self-insert', 'a'), ['a']),
+ ( 'accept', ['a'])])
+
+ def test_repeat(self):
+ self.run_test([(('digit-arg', '3'), ['']),
+ (('self-insert', 'a'), ['aaa']),
+ ( 'accept', ['aaa'])])
+
+ def test_kill_line(self):
+ self.run_test([(('self-insert', 'abc'), ['abc']),
+ ( 'left', None),
+ ( 'kill-line', ['ab']),
+ ( 'accept', ['ab'])])
+
+ def test_unix_line_discard(self):
+ self.run_test([(('self-insert', 'abc'), ['abc']),
+ ( 'left', None),
+ ( 'unix-word-rubout', ['c']),
+ ( 'accept', ['c'])])
+
+ def test_kill_word(self):
+ self.run_test([(('self-insert', 'ab cd'), ['ab cd']),
+ ( 'beginning-of-line', ['ab cd']),
+ ( 'kill-word', [' cd']),
+ ( 'accept', [' cd'])])
+
+ def test_backward_kill_word(self):
+ self.run_test([(('self-insert', 'ab cd'), ['ab cd']),
+ ( 'backward-kill-word', ['ab ']),
+ ( 'accept', ['ab '])])
+
+ def test_yank(self):
+ self.run_test([(('self-insert', 'ab cd'), ['ab cd']),
+ ( 'backward-kill-word', ['ab ']),
+ ( 'beginning-of-line', ['ab ']),
+ ( 'yank', ['cdab ']),
+ ( 'accept', ['cdab '])])
+
+ def test_yank_pop(self):
+ self.run_test([(('self-insert', 'ab cd'), ['ab cd']),
+ ( 'backward-kill-word', ['ab ']),
+ ( 'left', ['ab ']),
+ ( 'backward-kill-word', [' ']),
+ ( 'yank', ['ab ']),
+ ( 'yank-pop', ['cd ']),
+ ( 'accept', ['cd '])])
+
+ def test_interrupt(self):
+ try:
+ self.run_test([( 'interrupt', [''])])
+ except KeyboardInterrupt:
+ pass
+ else:
+ self.fail('KeyboardInterrupt got lost')
+
+ # test_suspend -- hah
+
+ def test_up(self):
+ self.run_test([(('self-insert', 'ab\ncd'), ['ab', 'cd']),
+ ( 'up', ['ab', 'cd']),
+ (('self-insert', 'e'), ['abe', 'cd']),
+ ( 'accept', ['abe', 'cd'])])
+
+ def test_down(self):
+ self.run_test([(('self-insert', 'ab\ncd'), ['ab', 'cd']),
+ ( 'up', ['ab', 'cd']),
+ (('self-insert', 'e'), ['abe', 'cd']),
+ ( 'down', ['abe', 'cd']),
+ (('self-insert', 'f'), ['abe', 'cdf']),
+ ( 'accept', ['abe', 'cdf'])])
+
+ def test_left(self):
+ self.run_test([(('self-insert', 'ab'), ['ab']),
+ ( 'left', ['ab']),
+ (('self-insert', 'c'), ['acb']),
+ ( 'accept', ['acb'])])
+
+ def test_right(self):
+ self.run_test([(('self-insert', 'ab'), ['ab']),
+ ( 'left', ['ab']),
+ (('self-insert', 'c'), ['acb']),
+ ( 'right', ['acb']),
+ (('self-insert', 'd'), ['acbd']),
+ ( 'accept', ['acbd'])])
+
+def test():
+ run_testcase(SimpleTestCase)
+
+if __name__ == '__main__':
+ test()
diff --git a/lib_pypy/pyrepl/tests/bugs.py b/lib_pypy/pyrepl/tests/bugs.py
new file mode 100644
index 0000000000..434253cc1e
--- /dev/null
+++ b/lib_pypy/pyrepl/tests/bugs.py
@@ -0,0 +1,36 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from pyrepl.console import Event
+from pyrepl.tests.infrastructure import ReaderTestCase, EA, run_testcase
+
+# this test case should contain as-verbatim-as-possible versions of
+# (applicable) bug reports
+
+class BugsTestCase(ReaderTestCase):
+
+ def test_transpose_at_start(self):
+ self.run_test([( 'transpose', [EA, '']),
+ ( 'accept', [''])])
+
+def test():
+ run_testcase(BugsTestCase)
+
+if __name__ == '__main__':
+ test()
diff --git a/lib_pypy/pyrepl/tests/infrastructure.py b/lib_pypy/pyrepl/tests/infrastructure.py
new file mode 100644
index 0000000000..c938b33b7e
--- /dev/null
+++ b/lib_pypy/pyrepl/tests/infrastructure.py
@@ -0,0 +1,82 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from pyrepl.reader import Reader
+from pyrepl.console import Console, Event
+import unittest
+import sys
+
+class EqualsAnything(object):
+ def __eq__(self, other):
+ return True
+EA = EqualsAnything()
+
+class TestConsole(Console):
+ height = 24
+ width = 80
+ encoding = 'utf-8'
+
+ def __init__(self, events, testcase, verbose=False):
+ self.events = events
+ self.next_screen = None
+ self.verbose = verbose
+ self.testcase = testcase
+
+ def refresh(self, screen, xy):
+ if self.next_screen is not None:
+ self.testcase.assertEqual(
+ screen, self.next_screen,
+ "[ %s != %s after %r ]"%(screen, self.next_screen,
+ self.last_event_name))
+
+ def get_event(self, block=1):
+ ev, sc = self.events.pop(0)
+ self.next_screen = sc
+ if not isinstance(ev, tuple):
+ ev = (ev,)
+ self.last_event_name = ev[0]
+ if self.verbose:
+ print "event", ev
+ return Event(*ev)
+
+class TestReader(Reader):
+ def get_prompt(self, lineno, cursor_on_line):
+ return ''
+ def refresh(self):
+ Reader.refresh(self)
+ self.dirty = True
+
+class ReaderTestCase(unittest.TestCase):
+ def run_test(self, test_spec, reader_class=TestReader):
+ # remember to finish your test_spec with 'accept' or similar!
+ con = TestConsole(test_spec, self)
+ reader = reader_class(con)
+ reader.readline()
+
+class BasicTestRunner:
+ def run(self, test):
+ result = unittest.TestResult()
+ test(result)
+ return result
+
+def run_testcase(testclass):
+ suite = unittest.makeSuite(testclass)
+ runner = unittest.TextTestRunner(sys.stdout, verbosity=1)
+ result = runner.run(suite)
+
diff --git a/lib_pypy/pyrepl/tests/wishes.py b/lib_pypy/pyrepl/tests/wishes.py
new file mode 100644
index 0000000000..81e46656c0
--- /dev/null
+++ b/lib_pypy/pyrepl/tests/wishes.py
@@ -0,0 +1,38 @@
+# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from pyrepl.console import Event
+from pyrepl.tests.infrastructure import ReaderTestCase, EA, run_testcase
+
+# this test case should contain as-verbatim-as-possible versions of
+# (applicable) feature requests
+
+class WishesTestCase(ReaderTestCase):
+
+ def test_quoted_insert_repeat(self):
+ self.run_test([(('digit-arg', '3'), ['']),
+ ( 'quoted-insert', ['']),
+ (('self-insert', '\033'), ['^[^[^[']),
+ ( 'accept', None)])
+
+def test():
+ run_testcase(WishesTestCase)
+
+if __name__ == '__main__':
+ test()
diff --git a/lib_pypy/pyrepl/unicodedata_.py b/lib_pypy/pyrepl/unicodedata_.py
new file mode 100644
index 0000000000..04e6c97030
--- /dev/null
+++ b/lib_pypy/pyrepl/unicodedata_.py
@@ -0,0 +1,59 @@
+try:
+ from unicodedata import *
+except ImportError:
+
+ def category(ch):
+ """
+ ASCII only implementation
+ """
+ if type(ch) is not unicode:
+ raise TypeError
+ if len(ch) != 1:
+ raise TypeError
+ return _categories.get(ord(ch), 'Co') # "Other, private use"
+
+ _categories = {
+ 0: 'Cc', 1: 'Cc', 2: 'Cc', 3: 'Cc', 4: 'Cc', 5: 'Cc',
+ 6: 'Cc', 7: 'Cc', 8: 'Cc', 9: 'Cc', 10: 'Cc', 11: 'Cc',
+ 12: 'Cc', 13: 'Cc', 14: 'Cc', 15: 'Cc', 16: 'Cc', 17: 'Cc',
+ 18: 'Cc', 19: 'Cc', 20: 'Cc', 21: 'Cc', 22: 'Cc', 23: 'Cc',
+ 24: 'Cc', 25: 'Cc', 26: 'Cc', 27: 'Cc', 28: 'Cc', 29: 'Cc',
+ 30: 'Cc', 31: 'Cc', 32: 'Zs', 33: 'Po', 34: 'Po', 35: 'Po',
+ 36: 'Sc', 37: 'Po', 38: 'Po', 39: 'Po', 40: 'Ps', 41: 'Pe',
+ 42: 'Po', 43: 'Sm', 44: 'Po', 45: 'Pd', 46: 'Po', 47: 'Po',
+ 48: 'Nd', 49: 'Nd', 50: 'Nd', 51: 'Nd', 52: 'Nd', 53: 'Nd',
+ 54: 'Nd', 55: 'Nd', 56: 'Nd', 57: 'Nd', 58: 'Po', 59: 'Po',
+ 60: 'Sm', 61: 'Sm', 62: 'Sm', 63: 'Po', 64: 'Po', 65: 'Lu',
+ 66: 'Lu', 67: 'Lu', 68: 'Lu', 69: 'Lu', 70: 'Lu', 71: 'Lu',
+ 72: 'Lu', 73: 'Lu', 74: 'Lu', 75: 'Lu', 76: 'Lu', 77: 'Lu',
+ 78: 'Lu', 79: 'Lu', 80: 'Lu', 81: 'Lu', 82: 'Lu', 83: 'Lu',
+ 84: 'Lu', 85: 'Lu', 86: 'Lu', 87: 'Lu', 88: 'Lu', 89: 'Lu',
+ 90: 'Lu', 91: 'Ps', 92: 'Po', 93: 'Pe', 94: 'Sk', 95: 'Pc',
+ 96: 'Sk', 97: 'Ll', 98: 'Ll', 99: 'Ll', 100: 'Ll', 101: 'Ll',
+ 102: 'Ll', 103: 'Ll', 104: 'Ll', 105: 'Ll', 106: 'Ll', 107: 'Ll',
+ 108: 'Ll', 109: 'Ll', 110: 'Ll', 111: 'Ll', 112: 'Ll', 113: 'Ll',
+ 114: 'Ll', 115: 'Ll', 116: 'Ll', 117: 'Ll', 118: 'Ll', 119: 'Ll',
+ 120: 'Ll', 121: 'Ll', 122: 'Ll', 123: 'Ps', 124: 'Sm', 125: 'Pe',
+ 126: 'Sm', 127: 'Cc', 128: 'Cc', 129: 'Cc', 130: 'Cc', 131: 'Cc',
+ 132: 'Cc', 133: 'Cc', 134: 'Cc', 135: 'Cc', 136: 'Cc', 137: 'Cc',
+ 138: 'Cc', 139: 'Cc', 140: 'Cc', 141: 'Cc', 142: 'Cc', 143: 'Cc',
+ 144: 'Cc', 145: 'Cc', 146: 'Cc', 147: 'Cc', 148: 'Cc', 149: 'Cc',
+ 150: 'Cc', 151: 'Cc', 152: 'Cc', 153: 'Cc', 154: 'Cc', 155: 'Cc',
+ 156: 'Cc', 157: 'Cc', 158: 'Cc', 159: 'Cc', 160: 'Zs', 161: 'Po',
+ 162: 'Sc', 163: 'Sc', 164: 'Sc', 165: 'Sc', 166: 'So', 167: 'So',
+ 168: 'Sk', 169: 'So', 170: 'Ll', 171: 'Pi', 172: 'Sm', 173: 'Cf',
+ 174: 'So', 175: 'Sk', 176: 'So', 177: 'Sm', 178: 'No', 179: 'No',
+ 180: 'Sk', 181: 'Ll', 182: 'So', 183: 'Po', 184: 'Sk', 185: 'No',
+ 186: 'Ll', 187: 'Pf', 188: 'No', 189: 'No', 190: 'No', 191: 'Po',
+ 192: 'Lu', 193: 'Lu', 194: 'Lu', 195: 'Lu', 196: 'Lu', 197: 'Lu',
+ 198: 'Lu', 199: 'Lu', 200: 'Lu', 201: 'Lu', 202: 'Lu', 203: 'Lu',
+ 204: 'Lu', 205: 'Lu', 206: 'Lu', 207: 'Lu', 208: 'Lu', 209: 'Lu',
+ 210: 'Lu', 211: 'Lu', 212: 'Lu', 213: 'Lu', 214: 'Lu', 215: 'Sm',
+ 216: 'Lu', 217: 'Lu', 218: 'Lu', 219: 'Lu', 220: 'Lu', 221: 'Lu',
+ 222: 'Lu', 223: 'Ll', 224: 'Ll', 225: 'Ll', 226: 'Ll', 227: 'Ll',
+ 228: 'Ll', 229: 'Ll', 230: 'Ll', 231: 'Ll', 232: 'Ll', 233: 'Ll',
+ 234: 'Ll', 235: 'Ll', 236: 'Ll', 237: 'Ll', 238: 'Ll', 239: 'Ll',
+ 240: 'Ll', 241: 'Ll', 242: 'Ll', 243: 'Ll', 244: 'Ll', 245: 'Ll',
+ 246: 'Ll', 247: 'Sm', 248: 'Ll', 249: 'Ll', 250: 'Ll', 251: 'Ll',
+ 252: 'Ll', 253: 'Ll', 254: 'Ll'
+ }
diff --git a/lib_pypy/pyrepl/unix_console.py b/lib_pypy/pyrepl/unix_console.py
new file mode 100644
index 0000000000..3297565930
--- /dev/null
+++ b/lib_pypy/pyrepl/unix_console.py
@@ -0,0 +1,558 @@
+# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
+# Antonio Cuni
+# Armin Rigo
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import termios, select, os, struct, errno
+import signal, re, time, sys
+from fcntl import ioctl
+from pyrepl import curses
+from pyrepl.fancy_termios import tcgetattr, tcsetattr
+from pyrepl.console import Console, Event
+from pyrepl import unix_eventqueue
+
+_error = (termios.error, curses.error)
+
+# there are arguments for changing this to "refresh"
+SIGWINCH_EVENT = 'repaint'
+
+FIONREAD = getattr(termios, "FIONREAD", None)
+TIOCGWINSZ = getattr(termios, "TIOCGWINSZ", None)
+
+def _my_getstr(cap, optional=0):
+ r = curses.tigetstr(cap)
+ if not optional and r is None:
+ raise RuntimeError, \
+ "terminal doesn't have the required '%s' capability"%cap
+ return r
+
+# at this point, can we say: AAAAAAAAAAAAAAAAAAAAAARGH!
+def maybe_add_baudrate(dict, rate):
+ name = 'B%d'%rate
+ if hasattr(termios, name):
+ dict[getattr(termios, name)] = rate
+
+ratedict = {}
+for r in [0, 110, 115200, 1200, 134, 150, 1800, 19200, 200, 230400,
+ 2400, 300, 38400, 460800, 4800, 50, 57600, 600, 75, 9600]:
+ maybe_add_baudrate(ratedict, r)
+
+del r, maybe_add_baudrate
+
+delayprog = re.compile("\\$<([0-9]+)((?:/|\\*){0,2})>")
+
+try:
+ poll = select.poll
+except AttributeError:
+ # this is exactly the minumum necessary to support what we
+ # do with poll objects
+ class poll:
+ def __init__(self):
+ pass
+ def register(self, fd, flag):
+ self.fd = fd
+ def poll(self, timeout=None):
+ r,w,e = select.select([self.fd],[],[],timeout)
+ return r
+
+POLLIN = getattr(select, "POLLIN", None)
+
+class UnixConsole(Console):
+ def __init__(self, f_in=0, f_out=1, term=None, encoding=None):
+ if encoding is None:
+ encoding = sys.getdefaultencoding()
+
+ self.encoding = encoding
+
+ if isinstance(f_in, int):
+ self.input_fd = f_in
+ else:
+ self.input_fd = f_in.fileno()
+
+ if isinstance(f_out, int):
+ self.output_fd = f_out
+ else:
+ self.output_fd = f_out.fileno()
+
+ self.pollob = poll()
+ self.pollob.register(self.input_fd, POLLIN)
+ curses.setupterm(term, self.output_fd)
+ self.term = term
+
+ self._bel = _my_getstr("bel")
+ self._civis = _my_getstr("civis", optional=1)
+ self._clear = _my_getstr("clear")
+ self._cnorm = _my_getstr("cnorm", optional=1)
+ self._cub = _my_getstr("cub", optional=1)
+ self._cub1 = _my_getstr("cub1", 1)
+ self._cud = _my_getstr("cud", 1)
+ self._cud1 = _my_getstr("cud1", 1)
+ self._cuf = _my_getstr("cuf", 1)
+ self._cuf1 = _my_getstr("cuf1", 1)
+ self._cup = _my_getstr("cup")
+ self._cuu = _my_getstr("cuu", 1)
+ self._cuu1 = _my_getstr("cuu1", 1)
+ self._dch1 = _my_getstr("dch1", 1)
+ self._dch = _my_getstr("dch", 1)
+ self._el = _my_getstr("el")
+ self._hpa = _my_getstr("hpa", 1)
+ self._ich = _my_getstr("ich", 1)
+ self._ich1 = _my_getstr("ich1", 1)
+ self._ind = _my_getstr("ind", 1)
+ self._pad = _my_getstr("pad", 1)
+ self._ri = _my_getstr("ri", 1)
+ self._rmkx = _my_getstr("rmkx", 1)
+ self._smkx = _my_getstr("smkx", 1)
+
+ ## work out how we're going to sling the cursor around
+ if 0 and self._hpa: # hpa don't work in windows telnet :-(
+ self.__move_x = self.__move_x_hpa
+ elif self._cub and self._cuf:
+ self.__move_x = self.__move_x_cub_cuf
+ elif self._cub1 and self._cuf1:
+ self.__move_x = self.__move_x_cub1_cuf1
+ else:
+ raise RuntimeError, "insufficient terminal (horizontal)"
+
+ if self._cuu and self._cud:
+ self.__move_y = self.__move_y_cuu_cud
+ elif self._cuu1 and self._cud1:
+ self.__move_y = self.__move_y_cuu1_cud1
+ else:
+ raise RuntimeError, "insufficient terminal (vertical)"
+
+ if self._dch1:
+ self.dch1 = self._dch1
+ elif self._dch:
+ self.dch1 = curses.tparm(self._dch, 1)
+ else:
+ self.dch1 = None
+
+ if self._ich1:
+ self.ich1 = self._ich1
+ elif self._ich:
+ self.ich1 = curses.tparm(self._ich, 1)
+ else:
+ self.ich1 = None
+
+ self.__move = self.__move_short
+
+ self.event_queue = unix_eventqueue.EventQueue(self.input_fd)
+ self.partial_char = ''
+ self.cursor_visible = 1
+
+ def change_encoding(self, encoding):
+ self.encoding = encoding
+
+ def refresh(self, screen, (cx, cy)):
+ # this function is still too long (over 90 lines)
+
+ if not self.__gone_tall:
+ while len(self.screen) < min(len(screen), self.height):
+ self.__hide_cursor()
+ self.__move(0, len(self.screen) - 1)
+ self.__write("\n")
+ self.__posxy = 0, len(self.screen)
+ self.screen.append("")
+ else:
+ while len(self.screen) < len(screen):
+ self.screen.append("")
+
+ if len(screen) > self.height:
+ self.__gone_tall = 1
+ self.__move = self.__move_tall
+
+ px, py = self.__posxy
+ old_offset = offset = self.__offset
+ height = self.height
+
+ if 0:
+ global counter
+ try:
+ counter
+ except NameError:
+ counter = 0
+ self.__write_code(curses.tigetstr("setaf"), counter)
+ counter += 1
+ if counter > 8:
+ counter = 0
+
+ # we make sure the cursor is on the screen, and that we're
+ # using all of the screen if we can
+ if cy < offset:
+ offset = cy
+ elif cy >= offset + height:
+ offset = cy - height + 1
+ elif offset > 0 and len(screen) < offset + height:
+ offset = max(len(screen) - height, 0)
+ screen.append("")
+
+ oldscr = self.screen[old_offset:old_offset + height]
+ newscr = screen[offset:offset + height]
+
+ # use hardware scrolling if we have it.
+ if old_offset > offset and self._ri:
+ self.__hide_cursor()
+ self.__write_code(self._cup, 0, 0)
+ self.__posxy = 0, old_offset
+ for i in range(old_offset - offset):
+ self.__write_code(self._ri)
+ oldscr.pop(-1)
+ oldscr.insert(0, "")
+ elif old_offset < offset and self._ind:
+ self.__hide_cursor()
+ self.__write_code(self._cup, self.height - 1, 0)
+ self.__posxy = 0, old_offset + self.height - 1
+ for i in range(offset - old_offset):
+ self.__write_code(self._ind)
+ oldscr.pop(0)
+ oldscr.append("")
+
+ self.__offset = offset
+
+ for y, oldline, newline, in zip(range(offset, offset + height),
+ oldscr,
+ newscr):
+ if oldline != newline:
+ self.__write_changed_line(y, oldline, newline, px)
+
+ y = len(newscr)
+ while y < len(oldscr):
+ self.__hide_cursor()
+ self.__move(0, y)
+ self.__posxy = 0, y
+ self.__write_code(self._el)
+ y += 1
+
+ self.__show_cursor()
+
+ self.screen = screen
+ self.move_cursor(cx, cy)
+ self.flushoutput()
+
+ def __write_changed_line(self, y, oldline, newline, px):
+ # this is frustrating; there's no reason to test (say)
+ # self.dch1 inside the loop -- but alternative ways of
+ # structuring this function are equally painful (I'm trying to
+ # avoid writing code generators these days...)
+ x = 0
+ minlen = min(len(oldline), len(newline))
+ #
+ # reuse the oldline as much as possible, but stop as soon as we
+ # encounter an ESCAPE, because it might be the start of an escape
+ # sequene
+ while x < minlen and oldline[x] == newline[x] and newline[x] != '\x1b':
+ x += 1
+ if oldline[x:] == newline[x+1:] and self.ich1:
+ if ( y == self.__posxy[1] and x > self.__posxy[0]
+ and oldline[px:x] == newline[px+1:x+1] ):
+ x = px
+ self.__move(x, y)
+ self.__write_code(self.ich1)
+ self.__write(newline[x])
+ self.__posxy = x + 1, y
+ elif x < minlen and oldline[x + 1:] == newline[x + 1:]:
+ self.__move(x, y)
+ self.__write(newline[x])
+ self.__posxy = x + 1, y
+ elif (self.dch1 and self.ich1 and len(newline) == self.width
+ and x < len(newline) - 2
+ and newline[x+1:-1] == oldline[x:-2]):
+ self.__hide_cursor()
+ self.__move(self.width - 2, y)
+ self.__posxy = self.width - 2, y
+ self.__write_code(self.dch1)
+ self.__move(x, y)
+ self.__write_code(self.ich1)
+ self.__write(newline[x])
+ self.__posxy = x + 1, y
+ else:
+ self.__hide_cursor()
+ self.__move(x, y)
+ if len(oldline) > len(newline):
+ self.__write_code(self._el)
+ self.__write(newline[x:])
+ self.__posxy = len(newline), y
+
+ def __write(self, text):
+ self.__buffer.append((text, 0))
+
+ def __write_code(self, fmt, *args):
+ self.__buffer.append((curses.tparm(fmt, *args), 1))
+
+ def __maybe_write_code(self, fmt, *args):
+ if fmt:
+ self.__write_code(fmt, *args)
+
+ def __move_y_cuu1_cud1(self, y):
+ dy = y - self.__posxy[1]
+ if dy > 0:
+ self.__write_code(dy*self._cud1)
+ elif dy < 0:
+ self.__write_code((-dy)*self._cuu1)
+
+ def __move_y_cuu_cud(self, y):
+ dy = y - self.__posxy[1]
+ if dy > 0:
+ self.__write_code(self._cud, dy)
+ elif dy < 0:
+ self.__write_code(self._cuu, -dy)
+
+ def __move_x_hpa(self, x):
+ if x != self.__posxy[0]:
+ self.__write_code(self._hpa, x)
+
+ def __move_x_cub1_cuf1(self, x):
+ dx = x - self.__posxy[0]
+ if dx > 0:
+ self.__write_code(self._cuf1*dx)
+ elif dx < 0:
+ self.__write_code(self._cub1*(-dx))
+
+ def __move_x_cub_cuf(self, x):
+ dx = x - self.__posxy[0]
+ if dx > 0:
+ self.__write_code(self._cuf, dx)
+ elif dx < 0:
+ self.__write_code(self._cub, -dx)
+
+ def __move_short(self, x, y):
+ self.__move_x(x)
+ self.__move_y(y)
+
+ def __move_tall(self, x, y):
+ assert 0 <= y - self.__offset < self.height, y - self.__offset
+ self.__write_code(self._cup, y - self.__offset, x)
+
+ def move_cursor(self, x, y):
+ if y < self.__offset or y >= self.__offset + self.height:
+ self.event_queue.insert(Event('scroll', None))
+ else:
+ self.__move(x, y)
+ self.__posxy = x, y
+ self.flushoutput()
+
+ def prepare(self):
+ # per-readline preparations:
+ self.__svtermstate = tcgetattr(self.input_fd)
+ raw = self.__svtermstate.copy()
+ raw.iflag &=~ (termios.BRKINT | termios.INPCK |
+ termios.ISTRIP | termios.IXON)
+ raw.oflag &=~ (termios.OPOST)
+ raw.cflag &=~ (termios.CSIZE|termios.PARENB)
+ raw.cflag |= (termios.CS8)
+ raw.lflag &=~ (termios.ICANON|termios.ECHO|
+ termios.IEXTEN|(termios.ISIG*1))
+ raw.cc[termios.VMIN] = 1
+ raw.cc[termios.VTIME] = 0
+ tcsetattr(self.input_fd, termios.TCSADRAIN, raw)
+
+ self.screen = []
+ self.height, self.width = self.getheightwidth()
+
+ self.__buffer = []
+
+ self.__posxy = 0, 0
+ self.__gone_tall = 0
+ self.__move = self.__move_short
+ self.__offset = 0
+
+ self.__maybe_write_code(self._smkx)
+
+ self.old_sigwinch = signal.signal(
+ signal.SIGWINCH, self.__sigwinch)
+
+ def restore(self):
+ self.__maybe_write_code(self._rmkx)
+ self.flushoutput()
+ tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate)
+
+ signal.signal(signal.SIGWINCH, self.old_sigwinch)
+
+ def __sigwinch(self, signum, frame):
+ self.height, self.width = self.getheightwidth()
+ self.event_queue.insert(Event('resize', None))
+
+ def push_char(self, char):
+ self.partial_char += char
+ try:
+ c = unicode(self.partial_char, self.encoding)
+ except UnicodeError, e:
+ if len(e.args) > 4 and \
+ e.args[4] == 'unexpected end of data':
+ pass
+ else:
+ raise
+ else:
+ self.partial_char = ''
+ self.event_queue.push(c)
+
+ def get_event(self, block=1):
+ while self.event_queue.empty():
+ while 1: # All hail Unix!
+ try:
+ self.push_char(os.read(self.input_fd, 1))
+ except (IOError, OSError), err:
+ if err.errno == errno.EINTR:
+ if not self.event_queue.empty():
+ return self.event_queue.get()
+ else:
+ continue
+ else:
+ raise
+ else:
+ break
+ if not block:
+ break
+ return self.event_queue.get()
+
+ def wait(self):
+ self.pollob.poll()
+
+ def set_cursor_vis(self, vis):
+ if vis:
+ self.__show_cursor()
+ else:
+ self.__hide_cursor()
+
+ def __hide_cursor(self):
+ if self.cursor_visible:
+ self.__maybe_write_code(self._civis)
+ self.cursor_visible = 0
+
+ def __show_cursor(self):
+ if not self.cursor_visible:
+ self.__maybe_write_code(self._cnorm)
+ self.cursor_visible = 1
+
+ def repaint_prep(self):
+ if not self.__gone_tall:
+ self.__posxy = 0, self.__posxy[1]
+ self.__write("\r")
+ ns = len(self.screen)*['\000'*self.width]
+ self.screen = ns
+ else:
+ self.__posxy = 0, self.__offset
+ self.__move(0, self.__offset)
+ ns = self.height*['\000'*self.width]
+ self.screen = ns
+
+ if TIOCGWINSZ:
+ def getheightwidth(self):
+ try:
+ return int(os.environ["LINES"]), int(os.environ["COLUMNS"])
+ except KeyError:
+ height, width = struct.unpack(
+ "hhhh", ioctl(self.input_fd, TIOCGWINSZ, "\000"*8))[0:2]
+ if not height: return 25, 80
+ return height, width
+ else:
+ def getheightwidth(self):
+ try:
+ return int(os.environ["LINES"]), int(os.environ["COLUMNS"])
+ except KeyError:
+ return 25, 80
+
+ def forgetinput(self):
+ termios.tcflush(self.input_fd, termios.TCIFLUSH)
+
+ def flushoutput(self):
+ for text, iscode in self.__buffer:
+ if iscode:
+ self.__tputs(text)
+ else:
+ os.write(self.output_fd, text.encode(self.encoding))
+ del self.__buffer[:]
+
+ def __tputs(self, fmt, prog=delayprog):
+ """A Python implementation of the curses tputs function; the
+ curses one can't really be wrapped in a sane manner.
+
+ I have the strong suspicion that this is complexity that
+ will never do anyone any good."""
+ # using .get() means that things will blow up
+ # only if the bps is actually needed (which I'm
+ # betting is pretty unlkely)
+ bps = ratedict.get(self.__svtermstate.ospeed)
+ while 1:
+ m = prog.search(fmt)
+ if not m:
+ os.write(self.output_fd, fmt)
+ break
+ x, y = m.span()
+ os.write(self.output_fd, fmt[:x])
+ fmt = fmt[y:]
+ delay = int(m.group(1))
+ if '*' in m.group(2):
+ delay *= self.height
+ if self._pad:
+ nchars = (bps*delay)/1000
+ os.write(self.output_fd, self._pad*nchars)
+ else:
+ time.sleep(float(delay)/1000.0)
+
+ def finish(self):
+ y = len(self.screen) - 1
+ while y >= 0 and not self.screen[y]:
+ y -= 1
+ self.__move(0, min(y, self.height + self.__offset - 1))
+ self.__write("\n\r")
+ self.flushoutput()
+
+ def beep(self):
+ self.__maybe_write_code(self._bel)
+ self.flushoutput()
+
+ if FIONREAD:
+ def getpending(self):
+ e = Event('key', '', '')
+
+ while not self.event_queue.empty():
+ e2 = self.event_queue.get()
+ e.data += e2.data
+ e.raw += e.raw
+
+ amount = struct.unpack(
+ "i", ioctl(self.input_fd, FIONREAD, "\0\0\0\0"))[0]
+ raw = unicode(os.read(self.input_fd, amount), self.encoding, 'replace')
+ e.data += raw
+ e.raw += raw
+ return e
+ else:
+ def getpending(self):
+ e = Event('key', '', '')
+
+ while not self.event_queue.empty():
+ e2 = self.event_queue.get()
+ e.data += e2.data
+ e.raw += e.raw
+
+ amount = 10000
+ raw = unicode(os.read(self.input_fd, amount), self.encoding, 'replace')
+ e.data += raw
+ e.raw += raw
+ return e
+
+ def clear(self):
+ self.__write_code(self._clear)
+ self.__gone_tall = 1
+ self.__move = self.__move_tall
+ self.__posxy = 0, 0
+ self.screen = []
+
diff --git a/lib_pypy/pyrepl/unix_eventqueue.py b/lib_pypy/pyrepl/unix_eventqueue.py
new file mode 100644
index 0000000000..84d0a97eae
--- /dev/null
+++ b/lib_pypy/pyrepl/unix_eventqueue.py
@@ -0,0 +1,86 @@
+# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
+# Armin Rigo
+#
+# All Rights Reserved
+#
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
+# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Bah, this would be easier to test if curses/terminfo didn't have so
+# much non-introspectable global state.
+
+from pyrepl import keymap
+from pyrepl.console import Event
+from pyrepl import curses
+from termios import tcgetattr, VERASE
+import os
+
+_keynames = {
+ "delete" : "kdch1",
+ "down" : "kcud1",
+ "end" : "kend",
+ "enter" : "kent",
+ "f1" : "kf1", "f2" : "kf2", "f3" : "kf3", "f4" : "kf4",
+ "f5" : "kf5", "f6" : "kf6", "f7" : "kf7", "f8" : "kf8",
+ "f9" : "kf9", "f10" : "kf10", "f11" : "kf11", "f12" : "kf12",
+ "f13" : "kf13", "f14" : "kf14", "f15" : "kf15", "f16" : "kf16",
+ "f17" : "kf17", "f18" : "kf18", "f19" : "kf19", "f20" : "kf20",
+ "home" : "khome",
+ "insert" : "kich1",
+ "left" : "kcub1",
+ "page down" : "knp",
+ "page up" : "kpp",
+ "right" : "kcuf1",
+ "up" : "kcuu1",
+ }
+
+class EventQueue(object):
+ def __init__(self, fd):
+ our_keycodes = {}
+ for key, tiname in _keynames.items():
+ keycode = curses.tigetstr(tiname)
+ if keycode:
+ our_keycodes[keycode] = unicode(key)
+ if os.isatty(fd):
+ our_keycodes[tcgetattr(fd)[6][VERASE]] = u'backspace'
+ self.k = self.ck = keymap.compile_keymap(our_keycodes)
+ self.events = []
+ self.buf = []
+ def get(self):
+ if self.events:
+ return self.events.pop(0)
+ else:
+ return None
+ def empty(self):
+ return not self.events
+ def insert(self, event):
+ self.events.append(event)
+ def push(self, char):
+ if char in self.k:
+ k = self.k[char]
+ if isinstance(k, dict):
+ self.buf.append(char)
+ self.k = k
+ else:
+ self.events.append(Event('key', k, ''.join(self.buf) + char))
+ self.buf = []
+ self.k = self.ck
+ elif self.buf:
+ self.events.extend([Event('key', c, c) for c in self.buf])
+ self.buf = []
+ self.k = self.ck
+ self.push(char)
+ else:
+ self.events.append(Event('key', char, char))