diff options
author | Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> | 2011-03-21 17:15:34 +0100 |
---|---|---|
committer | Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> | 2011-03-21 17:15:34 +0100 |
commit | 97216aa12ad931c89522381164c204c90a16c294 (patch) | |
tree | f92bce444cf3de878eb71babea59affa80d8349e /lib_pypy/pyrepl | |
parent | branch that kills the subrepos for using dependencies instead (diff) | |
download | pypy-97216aa12ad931c89522381164c204c90a16c294.tar.gz pypy-97216aa12ad931c89522381164c204c90a16c294.tar.bz2 pypy-97216aa12ad931c89522381164c204c90a16c294.zip |
inline pyrepl
Diffstat (limited to 'lib_pypy/pyrepl')
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)) |