0
|
1 |
# Copyright 2000-2004 Michael Hudson mwh@python.net
|
|
2 |
#
|
|
3 |
# All Rights Reserved
|
|
4 |
#
|
|
5 |
#
|
|
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.
|
|
11 |
#
|
|
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.
|
|
19 |
|
|
20 |
from pyrepl import commands, reader
|
|
21 |
from pyrepl.reader import Reader
|
|
22 |
|
|
23 |
def uniqify(l):
|
|
24 |
d = {}
|
|
25 |
for i in l:
|
|
26 |
d[i] = 1
|
|
27 |
r = d.keys()
|
|
28 |
r.sort()
|
|
29 |
return r
|
|
30 |
|
|
31 |
def prefix(wordlist, j = 0):
|
|
32 |
d = {}
|
|
33 |
i = j
|
|
34 |
try:
|
|
35 |
while 1:
|
|
36 |
for word in wordlist:
|
|
37 |
d[word[i]] = 1
|
|
38 |
if len(d) > 1:
|
|
39 |
return wordlist[0][j:i]
|
|
40 |
i += 1
|
|
41 |
d = {}
|
|
42 |
except IndexError:
|
|
43 |
return wordlist[0][j:i]
|
|
44 |
|
|
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
|
|
49 |
menu = []
|
|
50 |
i = start
|
|
51 |
for r in range(rows):
|
|
52 |
row = []
|
|
53 |
for col in range(cols):
|
|
54 |
row.append("[ %-*s ]"%(maxlen, wordlist[i][:maxlen]))
|
|
55 |
i += 1
|
|
56 |
if i >= len(wordlist):
|
|
57 |
break
|
|
58 |
menu.append( ''.join(row) )
|
|
59 |
if i >= len(wordlist):
|
|
60 |
i = 0
|
|
61 |
break
|
|
62 |
if r + 5 > cons.height:
|
|
63 |
menu.append(" %d more... "%(len(wordlist) - i))
|
|
64 |
break
|
|
65 |
return menu, i
|
|
66 |
|
|
67 |
# this gets somewhat user interface-y, and as a result the logic gets
|
|
68 |
# very convoluted.
|
|
69 |
#
|
|
70 |
# To summarise the summary of the summary:- people are a problem.
|
|
71 |
# -- The Hitch-Hikers Guide to the Galaxy, Episode 12
|
|
72 |
|
|
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
|
|
77 |
#
|
|
78 |
# if there's no possible completion, beep at the user and point this out.
|
|
79 |
# this is easy.
|
|
80 |
#
|
|
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.
|
|
83 |
#
|
|
84 |
# now it gets complicated.
|
|
85 |
#
|
|
86 |
# for the first press of a completion key:
|
|
87 |
# if there's a common prefix, stick it in.
|
|
88 |
|
|
89 |
# irrespective of whether anything got stuck in, if the word is now
|
|
90 |
# complete, show the "complete but not unique" message
|
|
91 |
|
|
92 |
# if there's no common prefix and if the word is not now complete,
|
|
93 |
# beep.
|
|
94 |
|
|
95 |
# common prefix -> yes no
|
|
96 |
# word complete \/
|
|
97 |
# yes "cbnu" "cbnu"
|
|
98 |
# no - beep
|
|
99 |
|
|
100 |
# for the second bang on the completion key
|
|
101 |
# there will necessarily be no common prefix
|
|
102 |
# show a menu of the choices.
|
|
103 |
|
|
104 |
# for subsequent bangs, rotate the menu around (if there are sufficient
|
|
105 |
# choices).
|
|
106 |
|
|
107 |
class complete(commands.Command):
|
|
108 |
def do(self):
|
|
109 |
r = self.reader
|
|
110 |
stem = r.get_stem()
|
|
111 |
if r.last_command_is(self.__class__):
|
|
112 |
completions = r.cmpltn_menu_choices
|
|
113 |
else:
|
|
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 ]"
|
|
122 |
r.dirty = 1
|
|
123 |
r.insert(completions[0][len(stem):])
|
|
124 |
else:
|
|
125 |
p = prefix(completions, len(stem))
|
|
126 |
if p <> '':
|
|
127 |
r.insert(p)
|
|
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)
|
|
133 |
r.dirty = 1
|
|
134 |
elif stem + p in completions:
|
|
135 |
r.msg = "[ complete but not unique ]"
|
|
136 |
r.dirty = 1
|
|
137 |
else:
|
|
138 |
r.msg = "[ not unique ]"
|
|
139 |
r.dirty = 1
|
|
140 |
|
|
141 |
class self_insert(commands.self_insert):
|
|
142 |
def do(self):
|
|
143 |
commands.self_insert.do(self)
|
|
144 |
r = self.reader
|
|
145 |
if r.cmpltn_menu_vis:
|
|
146 |
stem = r.get_stem()
|
|
147 |
if len(stem) < 1:
|
|
148 |
r.cmpltn_reset()
|
|
149 |
else:
|
|
150 |
completions = [w for w in r.cmpltn_menu_choices
|
|
151 |
if w.startswith(stem)]
|
|
152 |
if completions:
|
|
153 |
r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
|
|
154 |
r.console, completions, 0)
|
|
155 |
else:
|
|
156 |
r.cmpltn_reset()
|
|
157 |
|
|
158 |
class CompletingReader(Reader):
|
|
159 |
"""Adds completion support
|
|
160 |
|
|
161 |
Adds instance variables:
|
|
162 |
* cmpltn_menu, cmpltn_menu_vis, cmpltn_menu_end, cmpltn_choices:
|
|
163 |
*
|
|
164 |
"""
|
|
165 |
|
|
166 |
def collect_keymap(self):
|
|
167 |
return super(CompletingReader, self).collect_keymap() + (
|
|
168 |
(r'\t', 'complete'),)
|
|
169 |
|
|
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
|
|
178 |
|
|
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):
|
|
182 |
self.cmpltn_reset()
|
|
183 |
|
|
184 |
def calc_screen(self):
|
|
185 |
screen = super(CompletingReader, self).calc_screen()
|
|
186 |
if self.cmpltn_menu_vis:
|
|
187 |
ly = self.lxy[1]
|
|
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)
|
|
191 |
return screen
|
|
192 |
|
|
193 |
def finish(self):
|
|
194 |
super(CompletingReader, self).finish()
|
|
195 |
self.cmpltn_reset()
|
|
196 |
|
|
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 = []
|
|
202 |
|
|
203 |
def get_stem(self):
|
|
204 |
st = self.syntax_table
|
|
205 |
SW = reader.SYNTAX_WORD
|
|
206 |
b = self.buffer
|
|
207 |
p = self.pos - 1
|
|
208 |
while p >= 0 and st.get(b[p], SW) == SW:
|
|
209 |
p -= 1
|
|
210 |
return u''.join(b[p+1:self.pos])
|
|
211 |
|
|
212 |
def get_completions(self, stem):
|
|
213 |
return []
|
|
214 |
|
|
215 |
def test():
|
|
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()
|
|
221 |
reader.ps1 = "c**> "
|
|
222 |
reader.ps2 = "c/*> "
|
|
223 |
reader.ps3 = "c|*> "
|
|
224 |
reader.ps4 = "c\*> "
|
|
225 |
while reader.readline():
|
|
226 |
pass
|
|
227 |
|
|
228 |
if __name__=='__main__':
|
|
229 |
test()
|