0
|
1 |
# Copyright 2000-2004 Michael Hudson mwh@python.net
|
|
2 |
#
|
|
3 |
# All Rights Reserved
|
|
4 |
#
|
|
5 |
# Portions Copyright (c) 2005 Nokia Corporation
|
|
6 |
#
|
|
7 |
# Permission to use, copy, modify, and distribute this software and
|
|
8 |
# its documentation for any purpose is hereby granted without fee,
|
|
9 |
# provided that the above copyright notice appear in all copies and
|
|
10 |
# that both that copyright notice and this permission notice appear in
|
|
11 |
# supporting documentation.
|
|
12 |
#
|
|
13 |
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
|
14 |
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
15 |
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
|
16 |
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
|
17 |
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
|
18 |
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
19 |
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
20 |
|
|
21 |
import types
|
|
22 |
try:
|
|
23 |
import unicodedata
|
|
24 |
except ImportError:
|
|
25 |
import dumbunicodedata as unicodedata
|
|
26 |
from pyrepl import commands
|
|
27 |
import ascii
|
|
28 |
from pyrepl import input
|
|
29 |
|
|
30 |
def _make_unctrl_map():
|
|
31 |
uc_map = {}
|
|
32 |
for c in map(unichr, range(256)):
|
|
33 |
if unicodedata.category(c)[0] <> 'C':
|
|
34 |
uc_map[c] = c
|
|
35 |
for i in range(32):
|
|
36 |
c = unichr(i)
|
|
37 |
uc_map[c] = u'^' + unichr(ord('A') + i - 1)
|
|
38 |
uc_map['\177'] = u'^?'
|
|
39 |
for i in range(256):
|
|
40 |
c = unichr(i)
|
|
41 |
if not uc_map.has_key(c):
|
|
42 |
uc_map[c] = u'\\%03o'%i
|
|
43 |
return uc_map
|
|
44 |
|
|
45 |
# disp_str proved to be a bottleneck for large inputs, so it's been
|
|
46 |
# rewritten in C; it's not required though.
|
|
47 |
try:
|
|
48 |
raise ImportError # currently it's borked by the unicode support
|
|
49 |
|
|
50 |
from _pyrepl_utils import disp_str, init_unctrl_map
|
|
51 |
|
|
52 |
init_unctrl_map(_make_unctrl_map())
|
|
53 |
|
|
54 |
del init_unctrl_map
|
|
55 |
except ImportError:
|
|
56 |
def _my_unctrl(c, u=_make_unctrl_map()):
|
|
57 |
# import unicodedata
|
|
58 |
if c in u:
|
|
59 |
return u[c]
|
|
60 |
else:
|
|
61 |
if unicodedata.category(c).startswith('C'):
|
|
62 |
return '\u%04x'%(ord(c),)
|
|
63 |
else:
|
|
64 |
return c
|
|
65 |
|
|
66 |
def disp_str(buffer, join=''.join, uc=_my_unctrl):
|
|
67 |
""" disp_str(buffer:string) -> (string, [int])
|
|
68 |
|
|
69 |
Return the string that should be the printed represenation of
|
|
70 |
|buffer| and a list detailing where the characters of |buffer|
|
|
71 |
get used up. E.g.:
|
|
72 |
|
|
73 |
>>> disp_str(chr(3))
|
|
74 |
('^C', [1, 0])
|
|
75 |
|
|
76 |
the list always contains 0s or 1s at present; it could conceivably
|
|
77 |
go higher as and when unicode support happens."""
|
|
78 |
s = map(uc, buffer)
|
|
79 |
return (join(s),
|
|
80 |
map(ord, join(map(lambda x:'\001'+(len(x)-1)*'\000', s))))
|
|
81 |
|
|
82 |
del _my_unctrl
|
|
83 |
|
|
84 |
del _make_unctrl_map
|
|
85 |
|
|
86 |
# syntax classes:
|
|
87 |
|
|
88 |
[SYNTAX_WHITESPACE,
|
|
89 |
SYNTAX_WORD,
|
|
90 |
SYNTAX_SYMBOL] = range(3)
|
|
91 |
|
|
92 |
def make_default_syntax_table():
|
|
93 |
# XXX perhaps should use some unicodedata here?
|
|
94 |
st = {}
|
|
95 |
for c in map(unichr, range(256)):
|
|
96 |
st[c] = SYNTAX_SYMBOL
|
|
97 |
for c in [a for a in map(unichr, range(256)) if a.isalpha()]:
|
|
98 |
st[c] = SYNTAX_WORD
|
|
99 |
st[u'\n'] = st[u' '] = SYNTAX_WHITESPACE
|
|
100 |
return st
|
|
101 |
|
|
102 |
default_keymap = tuple(
|
|
103 |
[(r'\C-a', 'beginning-of-line'),
|
|
104 |
(r'\C-b', 'left'),
|
|
105 |
(r'\C-c', 'interrupt'),
|
|
106 |
(r'\C-d', 'delete'),
|
|
107 |
(r'\C-e', 'end-of-line'),
|
|
108 |
(r'\C-f', 'right'),
|
|
109 |
(r'\C-g', 'cancel'),
|
|
110 |
(r'\C-h', 'backspace'),
|
|
111 |
(r'\C-j', 'accept'),
|
|
112 |
(r'\<return>', 'accept'),
|
|
113 |
(r'\C-k', 'kill-line'),
|
|
114 |
(r'\C-l', 'clear-screen'),
|
|
115 |
(r'\C-m', 'accept'),
|
|
116 |
(r'\C-q', 'quoted-insert'),
|
|
117 |
(r'\C-t', 'transpose-characters'),
|
|
118 |
(r'\C-u', 'unix-line-discard'),
|
|
119 |
(r'\C-v', 'quoted-insert'),
|
|
120 |
(r'\C-w', 'unix-word-rubout'),
|
|
121 |
(r'\C-x\C-s', 'save-history'),
|
|
122 |
(r'\C-x\C-u', 'upcase-region'),
|
|
123 |
(r'\C-y', 'yank'),
|
|
124 |
(r'\C-z', 'suspend'),
|
|
125 |
|
|
126 |
(r'\M-b', 'backward-word'),
|
|
127 |
(r'\M-c', 'capitalize-word'),
|
|
128 |
(r'\M-d', 'kill-word'),
|
|
129 |
(r'\M-f', 'forward-word'),
|
|
130 |
(r'\M-l', 'downcase-word'),
|
|
131 |
(r'\M-t', 'transpose-words'),
|
|
132 |
(r'\M-u', 'upcase-word'),
|
|
133 |
(r'\M-y', 'yank-pop'),
|
|
134 |
(r'\M--', 'digit-arg'),
|
|
135 |
(r'\M-0', 'digit-arg'),
|
|
136 |
(r'\M-1', 'digit-arg'),
|
|
137 |
(r'\M-2', 'digit-arg'),
|
|
138 |
(r'\M-3', 'digit-arg'),
|
|
139 |
(r'\M-4', 'digit-arg'),
|
|
140 |
(r'\M-5', 'digit-arg'),
|
|
141 |
(r'\M-6', 'digit-arg'),
|
|
142 |
(r'\M-7', 'digit-arg'),
|
|
143 |
(r'\M-8', 'digit-arg'),
|
|
144 |
(r'\M-9', 'digit-arg'),
|
|
145 |
(r'\M-\n', 'insert-nl'),
|
|
146 |
('\\\\', 'self-insert')] + \
|
|
147 |
[(c, 'self-insert')
|
|
148 |
for c in map(chr, range(32, 127)) if c <> '\\'] + \
|
|
149 |
[(c, 'self-insert')
|
|
150 |
for c in map(chr, range(128, 256)) if c.isalpha()] + \
|
|
151 |
[(r'\<up>', 'up'),
|
|
152 |
(r'\<down>', 'down'),
|
|
153 |
(r'\<left>', 'left'),
|
|
154 |
(r'\<right>', 'right'),
|
|
155 |
(r'\<insert>', 'quoted-insert'),
|
|
156 |
(r'\<delete>', 'delete'),
|
|
157 |
(r'\<backspace>', 'backspace'),
|
|
158 |
(r'\M-\<backspace>', 'backward-kill-word'),
|
|
159 |
(r'\<end>', 'end'),
|
|
160 |
(r'\<home>', 'home'),
|
|
161 |
(r'\<f1>', 'help'),
|
|
162 |
(r'\EOF', 'end'), # the entries in the terminfo database for xterms
|
|
163 |
(r'\EOH', 'home'), # seem to be wrong. this is a less than ideal
|
|
164 |
# workaround
|
|
165 |
])
|
|
166 |
|
|
167 |
del c # from the listcomps
|
|
168 |
|
|
169 |
class Reader(object):
|
|
170 |
"""The Reader class implements the bare bones of a command reader,
|
|
171 |
handling such details as editing and cursor motion. What it does
|
|
172 |
not support are such things as completion or history support -
|
|
173 |
these are implemented elsewhere.
|
|
174 |
|
|
175 |
Instance variables of note include:
|
|
176 |
|
|
177 |
* buffer:
|
|
178 |
A *list* (*not* a string at the moment :-) containing all the
|
|
179 |
characters that have been entered.
|
|
180 |
* console:
|
|
181 |
Hopefully encapsulates the OS dependent stuff.
|
|
182 |
* pos:
|
|
183 |
A 0-based index into `buffer' for where the insertion point
|
|
184 |
is.
|
|
185 |
* screeninfo:
|
|
186 |
Ahem. This list contains some info needed to move the
|
|
187 |
insertion point around reasonably efficiently. I'd like to
|
|
188 |
get rid of it, because its contents are obtuse (to put it
|
|
189 |
mildly) but I haven't worked out if that is possible yet.
|
|
190 |
* cxy, lxy:
|
|
191 |
the position of the insertion point in screen ... XXX
|
|
192 |
* syntax_table:
|
|
193 |
Dictionary mapping characters to `syntax class'; read the
|
|
194 |
emacs docs to see what this means :-)
|
|
195 |
* commands:
|
|
196 |
Dictionary mapping command names to command classes.
|
|
197 |
* arg:
|
|
198 |
The emacs-style prefix argument. It will be None if no such
|
|
199 |
argument has been provided.
|
|
200 |
* dirty:
|
|
201 |
True if we need to refresh the display.
|
|
202 |
* kill_ring:
|
|
203 |
The emacs-style kill-ring; manipulated with yank & yank-pop
|
|
204 |
* ps1, ps2, ps3, ps4:
|
|
205 |
prompts. ps1 is the prompt for a one-line input; for a
|
|
206 |
multiline input it looks like:
|
|
207 |
ps2> first line of input goes here
|
|
208 |
ps3> second and further
|
|
209 |
ps3> lines get ps3
|
|
210 |
...
|
|
211 |
ps4> and the last one gets ps4
|
|
212 |
As with the usual top-level, you can set these to instances if
|
|
213 |
you like; str() will be called on them (once) at the beginning
|
|
214 |
of each command. Don't put really long or newline containing
|
|
215 |
strings here, please!
|
|
216 |
This is just the default policy; you can change it freely by
|
|
217 |
overriding get_prompt() (and indeed some standard subclasses
|
|
218 |
do).
|
|
219 |
* finished:
|
|
220 |
handle1 will set this to a true value if a command signals
|
|
221 |
that we're done.
|
|
222 |
"""
|
|
223 |
|
|
224 |
help_text = """\
|
|
225 |
This is pyrepl. Hear my roar.
|
|
226 |
|
|
227 |
Helpful text may appear here at some point in the future when I'm
|
|
228 |
feeling more loquacious than I am now."""
|
|
229 |
|
|
230 |
def __init__(self, console):
|
|
231 |
self.buffer = []
|
|
232 |
self.ps1 = "->> "
|
|
233 |
self.ps2 = "/>> "
|
|
234 |
self.ps3 = "|.. "
|
|
235 |
self.ps4 = "\__ "
|
|
236 |
self.kill_ring = []
|
|
237 |
self.arg = None
|
|
238 |
self.finished = 0
|
|
239 |
self.console = console
|
|
240 |
self.commands = {}
|
|
241 |
self.msg = ''
|
|
242 |
for v in vars(commands).values():
|
|
243 |
if ( isinstance(v, type)
|
|
244 |
and issubclass(v, commands.Command)
|
|
245 |
and v.__name__[0].islower() ):
|
|
246 |
self.commands[v.__name__] = v
|
|
247 |
self.commands[v.__name__.replace('_', '-')] = v
|
|
248 |
self.syntax_table = make_default_syntax_table()
|
|
249 |
self.input_trans_stack = []
|
|
250 |
self.keymap = self.collect_keymap()
|
|
251 |
self.input_trans = input.KeymapTranslator(
|
|
252 |
self.keymap,
|
|
253 |
invalid_cls='invalid-key',
|
|
254 |
character_cls='self-insert')
|
|
255 |
|
|
256 |
def collect_keymap(self):
|
|
257 |
return default_keymap
|
|
258 |
|
|
259 |
def calc_screen(self):
|
|
260 |
"""The purpose of this method is to translate changes in
|
|
261 |
self.buffer into changes in self.screen. Currently it rips
|
|
262 |
everything down and starts from scratch, which whilst not
|
|
263 |
especially efficient is certainly simple(r).
|
|
264 |
"""
|
|
265 |
lines = self.get_unicode().split("\n")
|
|
266 |
screen = []
|
|
267 |
screeninfo = []
|
|
268 |
w = self.console.width - 1
|
|
269 |
p = self.pos
|
|
270 |
for ln, line in zip(range(len(lines)), lines):
|
|
271 |
ll = len(line)
|
|
272 |
if 0 <= p <= ll:
|
|
273 |
if self.msg:
|
|
274 |
for mline in self.msg.split("\n"):
|
|
275 |
screen.append(mline)
|
|
276 |
screeninfo.append((0, []))
|
|
277 |
self.lxy = p, ln
|
|
278 |
prompt = self.get_prompt(ln, ll >= p >= 0)
|
|
279 |
p -= ll + 1
|
|
280 |
lp = len(prompt)
|
|
281 |
l, l2 = disp_str(line)
|
|
282 |
wrapcount = (len(l) + lp) / w
|
|
283 |
if wrapcount == 0:
|
|
284 |
screen.append(prompt + l)
|
|
285 |
screeninfo.append((lp, l2+[1]))
|
|
286 |
else:
|
|
287 |
screen.append(prompt + l[:w-lp] + "\\")
|
|
288 |
screeninfo.append((lp, l2[:w-lp]))
|
|
289 |
for i in range(-lp + w, -lp + wrapcount*w, w):
|
|
290 |
screen.append(l[i:i+w] + "\\")
|
|
291 |
screeninfo.append((0, l2[i:i + w]))
|
|
292 |
screen.append(l[wrapcount*w - lp:])
|
|
293 |
screeninfo.append((0, l2[wrapcount*w - lp:]+[1]))
|
|
294 |
self.screeninfo = screeninfo
|
|
295 |
self.cxy = self.pos2xy(self.pos)
|
|
296 |
return screen
|
|
297 |
|
|
298 |
def bow(self, p=None):
|
|
299 |
"""Return the 0-based index of the word break preceding p most
|
|
300 |
immediately.
|
|
301 |
|
|
302 |
p defaults to self.pos; word boundaries are determined using
|
|
303 |
self.syntax_table."""
|
|
304 |
if p is None:
|
|
305 |
p = self.pos
|
|
306 |
st = self.syntax_table
|
|
307 |
b = self.buffer
|
|
308 |
p -= 1
|
|
309 |
while p >= 0 and st.get(b[p], SYNTAX_WORD) <> SYNTAX_WORD:
|
|
310 |
p -= 1
|
|
311 |
while p >= 0 and st.get(b[p], SYNTAX_WORD) == SYNTAX_WORD:
|
|
312 |
p -= 1
|
|
313 |
return p + 1
|
|
314 |
|
|
315 |
def eow(self, p=None):
|
|
316 |
"""Return the 0-based index of the word break following p most
|
|
317 |
immediately.
|
|
318 |
|
|
319 |
p defaults to self.pos; word boundaries are determined using
|
|
320 |
self.syntax_table."""
|
|
321 |
if p is None:
|
|
322 |
p = self.pos
|
|
323 |
st = self.syntax_table
|
|
324 |
b = self.buffer
|
|
325 |
while p < len(b) and st.get(b[p], SYNTAX_WORD) <> SYNTAX_WORD:
|
|
326 |
p += 1
|
|
327 |
while p < len(b) and st.get(b[p], SYNTAX_WORD) == SYNTAX_WORD:
|
|
328 |
p += 1
|
|
329 |
return p
|
|
330 |
|
|
331 |
def bol(self, p=None):
|
|
332 |
"""Return the 0-based index of the line break preceding p most
|
|
333 |
immediately.
|
|
334 |
|
|
335 |
p defaults to self.pos."""
|
|
336 |
# XXX there are problems here.
|
|
337 |
if p is None:
|
|
338 |
p = self.pos
|
|
339 |
b = self.buffer
|
|
340 |
p -= 1
|
|
341 |
while p >= 0 and b[p] <> '\n':
|
|
342 |
p -= 1
|
|
343 |
return p + 1
|
|
344 |
|
|
345 |
def eol(self, p=None):
|
|
346 |
"""Return the 0-based index of the line break following p most
|
|
347 |
immediately.
|
|
348 |
|
|
349 |
p defaults to self.pos."""
|
|
350 |
if p is None:
|
|
351 |
p = self.pos
|
|
352 |
b = self.buffer
|
|
353 |
while p < len(b) and b[p] <> '\n':
|
|
354 |
p += 1
|
|
355 |
return p
|
|
356 |
|
|
357 |
def get_arg(self, default=1):
|
|
358 |
"""Return any prefix argument that the user has supplied,
|
|
359 |
returning `default' if there is None. `default' defaults
|
|
360 |
(groan) to 1."""
|
|
361 |
if self.arg is None:
|
|
362 |
return default
|
|
363 |
else:
|
|
364 |
return self.arg
|
|
365 |
|
|
366 |
def get_prompt(self, lineno, cursor_on_line):
|
|
367 |
"""Return what should be in the left-hand margin for line
|
|
368 |
`lineno'."""
|
|
369 |
if self.arg is not None and cursor_on_line:
|
|
370 |
return "(arg: %s) "%self.arg
|
|
371 |
if "\n" in self.buffer:
|
|
372 |
if lineno == 0:
|
|
373 |
return self._ps2
|
|
374 |
elif lineno == self.buffer.count("\n"):
|
|
375 |
return self._ps4
|
|
376 |
else:
|
|
377 |
return self._ps3
|
|
378 |
else:
|
|
379 |
return self._ps1
|
|
380 |
|
|
381 |
def push_input_trans(self, itrans):
|
|
382 |
self.input_trans_stack.append(self.input_trans)
|
|
383 |
self.input_trans = itrans
|
|
384 |
|
|
385 |
def pop_input_trans(self):
|
|
386 |
self.input_trans = self.input_trans_stack.pop()
|
|
387 |
|
|
388 |
def pos2xy(self, pos):
|
|
389 |
"""Return the x, y coordinates of position 'pos'."""
|
|
390 |
# this *is* incomprehensible, yes.
|
|
391 |
y = 0
|
|
392 |
assert 0 <= pos <= len(self.buffer)
|
|
393 |
if pos == len(self.buffer):
|
|
394 |
y = len(self.screeninfo) - 1
|
|
395 |
p, l2 = self.screeninfo[y]
|
|
396 |
return p + len(l2) - 1, y
|
|
397 |
else:
|
|
398 |
for p, l2 in self.screeninfo:
|
|
399 |
l = l2.count(1)
|
|
400 |
if l > pos:
|
|
401 |
break
|
|
402 |
else:
|
|
403 |
pos -= l
|
|
404 |
y += 1
|
|
405 |
c = 0
|
|
406 |
i = 0
|
|
407 |
while c < pos:
|
|
408 |
c += l2[i]
|
|
409 |
i += 1
|
|
410 |
while l2[i] == 0:
|
|
411 |
i += 1
|
|
412 |
return p + i, y
|
|
413 |
|
|
414 |
def insert(self, text):
|
|
415 |
"""Insert 'text' at the insertion point."""
|
|
416 |
self.buffer[self.pos:self.pos] = list(text)
|
|
417 |
self.pos += len(text)
|
|
418 |
self.dirty = 1
|
|
419 |
|
|
420 |
def update_cursor(self):
|
|
421 |
"""Move the cursor to reflect changes in self.pos"""
|
|
422 |
self.cxy = self.pos2xy(self.pos)
|
|
423 |
self.console.move_cursor(*self.cxy)
|
|
424 |
|
|
425 |
def after_command(self, cmd):
|
|
426 |
"""This function is called to allow post command cleanup."""
|
|
427 |
if getattr(cmd, "kills_digit_arg", 1):
|
|
428 |
if self.arg is not None:
|
|
429 |
self.dirty = 1
|
|
430 |
self.arg = None
|
|
431 |
|
|
432 |
def prepare(self):
|
|
433 |
"""Get ready to run. Call restore when finished. You must not
|
|
434 |
write to the console in between the calls to prepare and
|
|
435 |
restore."""
|
|
436 |
try:
|
|
437 |
self.console.prepare()
|
|
438 |
self.arg = None
|
|
439 |
self.screeninfo = []
|
|
440 |
self.finished = 0
|
|
441 |
del self.buffer[:]
|
|
442 |
self.pos = 0
|
|
443 |
self.dirty = 1
|
|
444 |
self.last_command = None
|
|
445 |
self._ps1, self._ps2, self._ps3, self._ps4 = \
|
|
446 |
map(str, [self.ps1, self.ps2, self.ps3, self.ps4])
|
|
447 |
except:
|
|
448 |
self.restore()
|
|
449 |
raise
|
|
450 |
|
|
451 |
def last_command_is(self, klass):
|
|
452 |
if not self.last_command:
|
|
453 |
return 0
|
|
454 |
return issubclass(klass, self.last_command)
|
|
455 |
|
|
456 |
def restore(self):
|
|
457 |
"""Clean up after a run."""
|
|
458 |
self.console.restore()
|
|
459 |
|
|
460 |
def finish(self):
|
|
461 |
"""Called when a command signals that we're finished."""
|
|
462 |
pass
|
|
463 |
|
|
464 |
def message(self, msg="none"):
|
|
465 |
self.msg = "[ "+msg+" ] "
|
|
466 |
self.dirty = 1
|
|
467 |
|
|
468 |
def error(self, msg="none"):
|
|
469 |
self.msg = "! " + msg + " "
|
|
470 |
self.dirty = 1
|
|
471 |
self.console.beep()
|
|
472 |
|
|
473 |
def refresh(self):
|
|
474 |
"""Recalculate and refresh the screen."""
|
|
475 |
if self.console.isbusy():
|
|
476 |
return
|
|
477 |
# this call sets up self.cxy, so call it first.
|
|
478 |
screen = self.calc_screen()
|
|
479 |
self.console.refresh(screen, self.cxy)
|
|
480 |
self.dirty = 0 # forgot this for a while (blush)
|
|
481 |
|
|
482 |
def do_cmd(self, cmd):
|
|
483 |
if isinstance(cmd[0], str):
|
|
484 |
cmd = self.commands.get(cmd[0],
|
|
485 |
commands.invalid_command)(self, cmd)
|
|
486 |
elif isinstance(cmd[0], type):
|
|
487 |
cmd = cmd[0](self, cmd)
|
|
488 |
else:
|
|
489 |
cmd=cmd[0]
|
|
490 |
|
|
491 |
cmd.do()
|
|
492 |
|
|
493 |
self.after_command(cmd)
|
|
494 |
|
|
495 |
if self.dirty:
|
|
496 |
self.refresh()
|
|
497 |
else:
|
|
498 |
self.update_cursor()
|
|
499 |
|
|
500 |
if not isinstance(cmd, commands.digit_arg):
|
|
501 |
self.last_command = cmd.__class__
|
|
502 |
|
|
503 |
self.finished = cmd.finish
|
|
504 |
if self.finished:
|
|
505 |
self.console.finish()
|
|
506 |
self.finish()
|
|
507 |
|
|
508 |
def handle1(self, block=1):
|
|
509 |
"""Handle a single event. Wait as long as it takes if block
|
|
510 |
is true (the default), otherwise return None if no event is
|
|
511 |
pending."""
|
|
512 |
|
|
513 |
if self.msg:
|
|
514 |
self.msg = ''
|
|
515 |
self.dirty = 1
|
|
516 |
|
|
517 |
while 1:
|
|
518 |
event = self.console.get_event(block)
|
|
519 |
if not event: # can only happen if we're not blocking
|
|
520 |
return None
|
|
521 |
|
|
522 |
if event.evt == 'key':
|
|
523 |
self.input_trans.push(event)
|
|
524 |
elif event.evt == 'scroll':
|
|
525 |
self.refresh()
|
|
526 |
elif event.evt == 'resize':
|
|
527 |
self.refresh()
|
|
528 |
else:
|
|
529 |
pass
|
|
530 |
|
|
531 |
cmd = self.input_trans.get()
|
|
532 |
|
|
533 |
if cmd is None:
|
|
534 |
if block:
|
|
535 |
continue
|
|
536 |
else:
|
|
537 |
return None
|
|
538 |
|
|
539 |
self.do_cmd(cmd)
|
|
540 |
return 1
|
|
541 |
|
|
542 |
def readline(self):
|
|
543 |
"""Read a line. The implementation of this method also shows
|
|
544 |
how to drive Reader if you want more control over the event
|
|
545 |
loop."""
|
|
546 |
self.prepare()
|
|
547 |
try:
|
|
548 |
self.refresh()
|
|
549 |
while not self.finished:
|
|
550 |
self.handle1()
|
|
551 |
return self.get_buffer()
|
|
552 |
finally:
|
|
553 |
self.restore()
|
|
554 |
|
|
555 |
def bind(self, spec, command):
|
|
556 |
self.keymap = self.keymap + ((spec, command),)
|
|
557 |
self.input_trans = input.KeymapTranslator(
|
|
558 |
self.keymap,
|
|
559 |
invalid_cls='invalid-key',
|
|
560 |
character_cls='self-insert')
|
|
561 |
|
|
562 |
def get_buffer(self, encoding=None):
|
|
563 |
if encoding is None:
|
|
564 |
encoding = self.console.encoding
|
|
565 |
return u''.join(self.buffer).encode(self.console.encoding)
|
|
566 |
|
|
567 |
def get_unicode(self):
|
|
568 |
"""Return the current buffer as a unicode string."""
|
|
569 |
return u''.join(self.buffer)
|
|
570 |
|
|
571 |
def test():
|
|
572 |
from pyrepl.unix_console import UnixConsole
|
|
573 |
reader = Reader(UnixConsole())
|
|
574 |
reader.ps1 = "**> "
|
|
575 |
reader.ps2 = "/*> "
|
|
576 |
reader.ps3 = "|*> "
|
|
577 |
reader.ps4 = "\*> "
|
|
578 |
while reader.readline():
|
|
579 |
pass
|
|
580 |
|
|
581 |
if __name__=='__main__':
|
|
582 |
test()
|