|
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 from pyrepl import reader, commands |
|
22 from pyrepl.reader import Reader as R |
|
23 |
|
24 isearch_keymap = tuple( |
|
25 [('\\%03o'%c, 'isearch-end') for c in range(256) if chr(c) != '\\'] + \ |
|
26 [(c, 'isearch-add-character') |
|
27 for c in map(chr, range(32, 127)) if c != '\\'] + \ |
|
28 [('\\%03o'%c, 'isearch-add-character') |
|
29 for c in range(256) if chr(c).isalpha() and chr(c) != '\\'] + \ |
|
30 [('\\\\', 'self-insert'), |
|
31 (r'\C-r', 'isearch-backwards'), |
|
32 (r'\C-s', 'isearch-forwards'), |
|
33 (r'\C-c', 'isearch-cancel'), |
|
34 (r'\C-g', 'isearch-cancel'), |
|
35 (r'\<backspace>', 'isearch-backspace')]) |
|
36 |
|
37 del c |
|
38 |
|
39 ISEARCH_DIRECTION_NONE = '' |
|
40 ISEARCH_DIRECTION_BACKWARDS = 'r' |
|
41 ISEARCH_DIRECTION_FORWARDS = 'f' |
|
42 |
|
43 class next_history(commands.Command): |
|
44 def do(self): |
|
45 r = self.reader |
|
46 if r.historyi == len(r.history): |
|
47 r.error("end of history list") |
|
48 return |
|
49 r.select_item(r.historyi + 1) |
|
50 |
|
51 class previous_history(commands.Command): |
|
52 def do(self): |
|
53 r = self.reader |
|
54 if r.historyi == 0: |
|
55 r.error("start of history list") |
|
56 return |
|
57 r.select_item(r.historyi - 1) |
|
58 |
|
59 class restore_history(commands.Command): |
|
60 def do(self): |
|
61 r = self.reader |
|
62 if r.historyi != len(r.history): |
|
63 if r.get_unicode() != r.history[r.historyi]: |
|
64 r.buffer = list(r.history[r.historyi]) |
|
65 r.pos = len(r.buffer) |
|
66 r.dirty = 1 |
|
67 |
|
68 class first_history(commands.Command): |
|
69 def do(self): |
|
70 self.reader.select_item(0) |
|
71 |
|
72 class last_history(commands.Command): |
|
73 def do(self): |
|
74 self.reader.select_item(len(self.reader.history)) |
|
75 |
|
76 class operate_and_get_next(commands.FinishCommand): |
|
77 def do(self): |
|
78 self.reader.next_history = self.reader.historyi + 1 |
|
79 |
|
80 class yank_arg(commands.Command): |
|
81 def do(self): |
|
82 r = self.reader |
|
83 if r.last_command is self.__class__: |
|
84 r.yank_arg_i += 1 |
|
85 else: |
|
86 r.yank_arg_i = 0 |
|
87 if r.historyi <= r.yank_arg_i: |
|
88 r.error("beginning of history list") |
|
89 return |
|
90 a = r.get_arg(-1) |
|
91 # XXX how to split? |
|
92 words = r.get_item(r.historyi - r.yank_arg_i - 1).split() |
|
93 if a < -len(words) or a >= len(words): |
|
94 r.error("no such arg") |
|
95 return |
|
96 w = words[a] |
|
97 b = r.buffer |
|
98 if r.yank_arg_i > 0: |
|
99 o = len(r.yank_arg_yanked) |
|
100 else: |
|
101 o = 0 |
|
102 b[r.pos - o:r.pos] = list(w) |
|
103 r.yank_arg_yanked = w |
|
104 r.pos += len(w) - o |
|
105 r.dirty = 1 |
|
106 |
|
107 class forward_history_isearch(commands.Command): |
|
108 def do(self): |
|
109 r = self.reader |
|
110 r.isearch_direction = ISEARCH_DIRECTION_FORWARDS |
|
111 r.isearch_start = r.historyi, r.pos |
|
112 r.isearch_term = '' |
|
113 r.dirty = 1 |
|
114 r.push_input_trans(r.isearch_trans) |
|
115 |
|
116 |
|
117 class reverse_history_isearch(commands.Command): |
|
118 def do(self): |
|
119 r = self.reader |
|
120 r.isearch_direction = ISEARCH_DIRECTION_BACKWARDS |
|
121 r.dirty = 1 |
|
122 r.isearch_term = '' |
|
123 r.push_input_trans(r.isearch_trans) |
|
124 r.isearch_start = r.historyi, r.pos |
|
125 |
|
126 class isearch_cancel(commands.Command): |
|
127 def do(self): |
|
128 r = self.reader |
|
129 r.isearch_direction = ISEARCH_DIRECTION_NONE |
|
130 r.pop_input_trans() |
|
131 r.select_item(r.isearch_start[0]) |
|
132 r.pos = r.isearch_start[1] |
|
133 r.dirty = 1 |
|
134 |
|
135 class isearch_add_character(commands.Command): |
|
136 def do(self): |
|
137 r = self.reader |
|
138 b = r.buffer |
|
139 r.isearch_term += self.event[-1] |
|
140 r.dirty = 1 |
|
141 p = r.pos + len(r.isearch_term) - 1 |
|
142 if b[p:p+1] != [r.isearch_term[-1]]: |
|
143 r.isearch_next() |
|
144 |
|
145 class isearch_backspace(commands.Command): |
|
146 def do(self): |
|
147 r = self.reader |
|
148 if len(r.isearch_term) > 0: |
|
149 r.isearch_term = r.isearch_term[:-1] |
|
150 r.dirty = 1 |
|
151 else: |
|
152 r.error("nothing to rubout") |
|
153 |
|
154 class isearch_forwards(commands.Command): |
|
155 def do(self): |
|
156 r = self.reader |
|
157 r.isearch_direction = ISEARCH_DIRECTION_FORWARDS |
|
158 r.isearch_next() |
|
159 |
|
160 class isearch_backwards(commands.Command): |
|
161 def do(self): |
|
162 r = self.reader |
|
163 r.isearch_direction = ISEARCH_DIRECTION_BACKWARDS |
|
164 r.isearch_next() |
|
165 |
|
166 class isearch_end(commands.Command): |
|
167 def do(self): |
|
168 r = self.reader |
|
169 r.isearch_direction = ISEARCH_DIRECTION_NONE |
|
170 r.console.forgetinput() |
|
171 r.pop_input_trans() |
|
172 r.dirty = 1 |
|
173 r.isearch_previous_term = r.isearch_term |
|
174 |
|
175 class HistoricalReader(R): |
|
176 """Adds history support (with incremental history searching) to the |
|
177 Reader class. |
|
178 |
|
179 Adds the following instance variables: |
|
180 * history: |
|
181 a list of strings |
|
182 * historyi: |
|
183 * transient_history: |
|
184 * next_history: |
|
185 * isearch_direction, isearch_term, isearch_start: |
|
186 * yank_arg_i, yank_arg_yanked: |
|
187 used by the yank-arg command; not actually manipulated by any |
|
188 HistoricalReader instance methods. |
|
189 """ |
|
190 |
|
191 def collect_keymap(self): |
|
192 return super(HistoricalReader, self).collect_keymap() + ( |
|
193 (r'\C-n', 'next-history'), |
|
194 (r'\C-p', 'previous-history'), |
|
195 (r'\C-o', 'operate-and-get-next'), |
|
196 (r'\C-r', 'reverse-history-isearch'), |
|
197 (r'\C-s', 'forward-history-isearch'), |
|
198 (r'\M-r', 'restore-history'), |
|
199 (r'\M-.', 'yank-arg'), |
|
200 (r'\<page down>', 'last-history'), |
|
201 (r'\<page up>', 'first-history')) |
|
202 |
|
203 |
|
204 def __init__(self, console): |
|
205 super(HistoricalReader, self).__init__(console) |
|
206 self.history = [] |
|
207 self.historyi = 0 |
|
208 self.transient_history = {} |
|
209 self.next_history = None |
|
210 self.isearch_direction = ISEARCH_DIRECTION_NONE |
|
211 self.isearch_previous_term = '' |
|
212 for c in [next_history, previous_history, restore_history, |
|
213 first_history, last_history, yank_arg, |
|
214 forward_history_isearch, reverse_history_isearch, |
|
215 isearch_end, isearch_add_character, isearch_cancel, |
|
216 isearch_add_character, isearch_backspace, |
|
217 isearch_forwards, isearch_backwards, operate_and_get_next]: |
|
218 self.commands[c.__name__] = c |
|
219 self.commands[c.__name__.replace('_', '-')] = c |
|
220 from pyrepl import input |
|
221 self.isearch_trans = input.KeymapTranslator( |
|
222 isearch_keymap, invalid_cls=isearch_end, |
|
223 character_cls=isearch_add_character) |
|
224 |
|
225 def select_item(self, i): |
|
226 self.transient_history[self.historyi] = self.get_unicode() |
|
227 buf = self.transient_history.get(i) |
|
228 if buf is None: |
|
229 buf = self.history[i] |
|
230 self.buffer = list(buf) |
|
231 self.historyi = i |
|
232 self.pos = len(self.buffer) |
|
233 self.dirty = 1 |
|
234 |
|
235 def get_item(self, i): |
|
236 assert i>=0 and i<=len(self.history) |
|
237 if i < len(self.history): |
|
238 return self.transient_history.get(i, self.history[i]) |
|
239 else: |
|
240 return self.transient_history.get(i, self.get_unicode()) |
|
241 |
|
242 def prepare(self): |
|
243 super(HistoricalReader, self).prepare() |
|
244 try: |
|
245 self.transient_history = {} |
|
246 if self.next_history is not None \ |
|
247 and self.next_history < len(self.history): |
|
248 self.historyi = self.next_history |
|
249 self.buffer[:] = list(self.history[self.next_history]) |
|
250 self.pos = len(self.buffer) |
|
251 self.transient_history[len(self.history)] = '' |
|
252 else: |
|
253 self.historyi = len(self.history) |
|
254 self.next_history = None |
|
255 except: |
|
256 self.restore() |
|
257 raise |
|
258 |
|
259 def get_prompt(self, lineno, cursor_on_line): |
|
260 if cursor_on_line and self.isearch_direction <> ISEARCH_DIRECTION_NONE: |
|
261 d = 'rf'[self.isearch_direction == ISEARCH_DIRECTION_FORWARDS] |
|
262 return "(%s-search `%s') "%(d, self.isearch_term) |
|
263 else: |
|
264 return super(HistoricalReader, self).get_prompt(lineno, cursor_on_line) |
|
265 |
|
266 def isearch_next(self): |
|
267 st = self.isearch_term |
|
268 # If the isearch key is pressed twice without entering anything, |
|
269 # try to use the previous search term, just like Emacs does it. |
|
270 if st == '': |
|
271 self.isearch_term=st=self.isearch_previous_term |
|
272 if st=='': |
|
273 self.error("nothing to search") |
|
274 return |
|
275 p = self.pos |
|
276 i = self.historyi |
|
277 s = self.get_unicode() |
|
278 forwards = self.isearch_direction == ISEARCH_DIRECTION_FORWARDS |
|
279 while 1: |
|
280 if forwards: |
|
281 p = s.find(st, p + 1) |
|
282 else: |
|
283 p = s.rfind(st, 0, p + len(st) - 1) |
|
284 if p != -1: |
|
285 self.select_item(i) |
|
286 self.pos = p |
|
287 return |
|
288 elif ((forwards and i >= len(self.history) - 1) |
|
289 or (not forwards and i == 0)): |
|
290 self.error("not found") |
|
291 return |
|
292 else: |
|
293 if forwards: |
|
294 i += 1 |
|
295 s = self.get_item(i) |
|
296 p = -1 |
|
297 else: |
|
298 i -= 1 |
|
299 s = self.get_item(i) |
|
300 p = len(s) |
|
301 |
|
302 def finish(self): |
|
303 super(HistoricalReader, self).finish() |
|
304 ret = self.get_unicode() |
|
305 for i, t in self.transient_history.items(): |
|
306 if i < len(self.history) and i != self.historyi: |
|
307 self.history[i] = t |
|
308 if ret: |
|
309 self.history.append(ret) |
|
310 |
|
311 def test(): |
|
312 from pyrepl.unix_console import UnixConsole |
|
313 reader = HistoricalReader(UnixConsole()) |
|
314 reader.ps1 = "h**> " |
|
315 reader.ps2 = "h/*> " |
|
316 reader.ps3 = "h|*> " |
|
317 reader.ps4 = "h\*> " |
|
318 while reader.readline(): |
|
319 pass |
|
320 |
|
321 if __name__=='__main__': |
|
322 test() |