1
# Copyright 2000-2004 Michael Hudson mwh@python.net
6
# Permission to use, copy, modify, and distribute this software and
7
# its documentation for any purpose is hereby granted without fee,
8
# provided that the above copyright notice appear in all copies and
9
# that both that copyright notice and this permission notice appear in
10
# supporting documentation.
12
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
13
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
14
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
15
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
16
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
17
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20
from pyrepl import commands, reader
21
from pyrepl.reader import Reader
31
def prefix(wordlist, j = 0):
39
return wordlist[0][j:i]
43
return wordlist[0][j:i]
45
def build_menu(cons, wordlist, start):
46
maxlen = min(max(map(len, wordlist)), cons.width - 4)
47
cols = cons.width / (maxlen + 4)
48
rows = (len(wordlist) - 1)/cols + 1
53
for col in range(cols):
54
row.append("[ %-*s ]"%(maxlen, wordlist[i][:maxlen]))
56
if i >= len(wordlist):
58
menu.append( ''.join(row) )
59
if i >= len(wordlist):
62
if r + 5 > cons.height:
63
menu.append(" %d more... "%(len(wordlist) - i))
67
# this gets somewhat user interface-y, and as a result the logic gets
70
# To summarise the summary of the summary:- people are a problem.
71
# -- The Hitch-Hikers Guide to the Galaxy, Episode 12
73
#### Desired behaviour of the completions commands.
74
# the considerations are:
75
# (1) how many completions are possible
76
# (2) whether the last command was a completion
78
# if there's no possible completion, beep at the user and point this out.
81
# if there's only one possible completion, stick it in. if the last thing
82
# user did was a completion, point out that he isn't getting anywhere.
84
# now it gets complicated.
86
# for the first press of a completion key:
87
# if there's a common prefix, stick it in.
89
# irrespective of whether anything got stuck in, if the word is now
90
# complete, show the "complete but not unique" message
92
# if there's no common prefix and if the word is not now complete,
95
# common prefix -> yes no
100
# for the second bang on the completion key
101
# there will necessarily be no common prefix
102
# show a menu of the choices.
104
# for subsequent bangs, rotate the menu around (if there are sufficient
107
class complete(commands.Command):
111
if r.last_command_is(self.__class__):
112
completions = r.cmpltn_menu_choices
114
r.cmpltn_menu_choices = completions = \
115
r.get_completions(stem)
116
if len(completions) == 0:
117
r.error("no matches")
118
elif len(completions) == 1:
119
if len(completions[0]) == len(stem) and \
120
r.last_command_is(self.__class__):
121
r.msg = "[ sole completion ]"
123
r.insert(completions[0][len(stem):])
125
p = prefix(completions, len(stem))
128
if r.last_command_is(self.__class__):
129
if not r.cmpltn_menu_vis:
130
r.cmpltn_menu_vis = 1
131
r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
132
r.console, completions, r.cmpltn_menu_end)
134
elif stem + p in completions:
135
r.msg = "[ complete but not unique ]"
138
r.msg = "[ not unique ]"
141
class self_insert(commands.self_insert):
143
commands.self_insert.do(self)
145
if r.cmpltn_menu_vis:
150
completions = [w for w in r.cmpltn_menu_choices
151
if w.startswith(stem)]
153
r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
154
r.console, completions, 0)
158
class CompletingReader(Reader):
159
"""Adds completion support
161
Adds instance variables:
162
* cmpltn_menu, cmpltn_menu_vis, cmpltn_menu_end, cmpltn_choices:
166
def collect_keymap(self):
167
return super(CompletingReader, self).collect_keymap() + (
168
(r'\t', 'complete'),)
170
def __init__(self, console):
171
super(CompletingReader, self).__init__(console)
172
self.cmpltn_menu = ["[ menu 1 ]", "[ menu 2 ]"]
173
self.cmpltn_menu_vis = 0
174
self.cmpltn_menu_end = 0
175
for c in [complete, self_insert]:
176
self.commands[c.__name__] = c
177
self.commands[c.__name__.replace('_', '-')] = c
179
def after_command(self, cmd):
180
super(CompletingReader, self).after_command(cmd)
181
if not isinstance(cmd, complete) and not isinstance(cmd, self_insert):
184
def calc_screen(self):
185
screen = super(CompletingReader, self).calc_screen()
186
if self.cmpltn_menu_vis:
188
screen[ly:ly] = self.cmpltn_menu
189
self.screeninfo[ly:ly] = [(0, [])]*len(self.cmpltn_menu)
190
self.cxy = self.cxy[0], self.cxy[1] + len(self.cmpltn_menu)
194
super(CompletingReader, self).finish()
197
def cmpltn_reset(self):
198
self.cmpltn_menu = []
199
self.cmpltn_menu_vis = 0
200
self.cmpltn_menu_end = 0
201
self.cmpltn_menu_choices = []
204
st = self.syntax_table
205
SW = reader.SYNTAX_WORD
208
while p >= 0 and st.get(b[p], SW) == SW:
210
return u''.join(b[p+1:self.pos])
212
def get_completions(self, stem):
216
class TestReader(CompletingReader):
217
def get_completions(self, stem):
218
return [s for l in map(lambda x:x.split(),self.history)
219
for s in l if s and s.startswith(stem)]
220
reader = TestReader()
225
while reader.readline():
228
if __name__=='__main__':