diff -r 000000000000 -r ca70ae20a155 src/extras/pyrepl/completing_reader.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/extras/pyrepl/completing_reader.py Tue Feb 16 10:07:05 2010 +0530 @@ -0,0 +1,229 @@ +# Copyright 2000-2004 Michael Hudson mwh@python.net +# +# 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] + +def build_menu(cons, wordlist, start): + maxlen = min(max(map(len, wordlist)), cons.width - 4) + cols = cons.width / (maxlen + 4) + rows = (len(wordlist) - 1)/cols + 1 + menu = [] + i = start + for r in range(rows): + row = [] + for col in range(cols): + row.append("[ %-*s ]"%(maxlen, 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 +# +# 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. +# +# 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.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 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.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) + else: + r.cmpltn_reset() + +class CompletingReader(Reader): + """Adds completion support + + Adds instance variables: + * cmpltn_menu, cmpltn_menu_vis, cmpltn_menu_end, cmpltn_choices: + * + """ + + 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()