1# Copyright (c) 2010, Tomohiro Kusumi
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7# 1. Redistributions of source code must retain the above copyright notice, this
8#    list of conditions and the following disclaimer.
9# 2. Redistributions in binary form must reproduce the above copyright notice,
10#    this list of conditions and the following disclaimer in the documentation
11#    and/or other materials provided with the distribution.
12#
13# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
24from __future__ import division
25
26from . import candidate
27from . import history
28from . import kbd
29from . import literal
30from . import screen
31from . import util
32
33class Operand (object):
34    def __init__(self):
35        self.__history = history.History(None)
36        self.__prev = util.Namespace(key=kbd.ERROR, opc='', arg='', raw=[])
37        self.init([], [], [])
38        l = "e", "w", "wneg", "wq", "split", "vsplit", "bdelete", "open_b64e", \
39            "open_b64d", "open_b32e", "open_b32d", "open_b16e", "open_b16d", \
40            "open_b85e", "open_b85d" # XXX
41        self.__path_li = tuple(getattr(literal, "s_" + _) for _ in l)
42        self.__path_li_str = tuple(_.str for _ in self.__path_li)
43
44    def init(self, rl, fl, sl):
45        self.__regxs = rl
46        self.__fasts = fl
47        self.__slows = sl
48        _li_cand.init(self.__slows)
49        self.__cand = {None : _li_cand}
50        _arg_cand.init(literal.get_arg_literals())
51        for li in self.__slows:
52            if li is literal.s_set:
53                self.__cand[li.str] = _arg_cand
54            if li in self.__path_li:
55                self.__cand[li.str] = _path_cand
56        self.clear()
57
58    def cleanup(self):
59        self.__history.flush()
60
61    def clear(self):
62        self.__pos = 0
63        self.__amp = []
64        self.__buf = []
65        self.__opc = []
66        self.__arg = [] # list of strings
67        assert len(self.__buf) == self.__pos
68
69    def rewind(self):
70        end = self.__pos
71        beg = end - len(self.__opc)
72        l = self.__amp + self.__buf[:beg] + self.__buf[end:]
73        self.clear()
74        for x in l:
75            self.process_incoming(x)
76
77    def get_prev_history(self, c):
78        return self.__history.get_latest(c)
79
80    def __add_history(self):
81        key = chr(self.__buf[0])
82        s = _to_string(self.__buf)
83        if s != key:
84            if self.__history.get_latest(key) != s:
85                self.__history.append(key, s)
86            self.__history.reset_cursor(key)
87
88    def __get_older_history(self):
89        buf = self.__prev.raw
90        assert buf and buf[0] in literal.get_slow_ords()
91        x = _to_string(buf)
92        s = self.__history.get_older(x[0], x)
93        if not s:
94            s = self.__history.get_newer(x[0], x)
95            if not s:
96                s = x
97        assert s[0] == x[0]
98        self.__set_string(s)
99
100    def __get_newer_history(self):
101        buf = self.__prev.raw
102        assert buf and buf[0] in literal.get_slow_ords()
103        x = _to_string(buf)
104        s = self.__history.get_newer(x[0], x)
105        if not s:
106            s = x
107        assert s[0] == x[0]
108        self.__set_string(s)
109
110    def __clear_candidate(self):
111        self.__cand[None].clear()
112        for o in self.__cand.values():
113            o.clear()
114
115    def __get_min_cursor(self):
116        return 1
117
118    def __at_min_cursor(self):
119        return self.__pos <= self.__get_min_cursor()
120
121    def __get_max_cursor(self):
122        return screen.get_size_x() - 2
123
124    def __at_max_cursor(self):
125        return self.__pos >= self.__get_max_cursor()
126
127    def __get_tail_cursor(self):
128        return len(self.__buf)
129
130    def __at_tail_cursor(self):
131        return self.__pos == self.__get_tail_cursor()
132
133    def __add_buffer(self, c):
134        self.__buf.insert(self.__pos, c)
135        self.__pos += 1
136        self.__parse_buffer()
137
138    def __set_buffer(self, l):
139        self.clear()
140        self.__buf = list(l)
141        self.__pos = len(self.__buf)
142        self.__parse_buffer()
143
144    def __set_string(self, s):
145        self.__set_buffer([ord(x) for x in s])
146
147    def __parse_buffer(self):
148        self.__arg = []
149        if not _is_slow(self.__buf):
150            self.__opc = self.__buf
151        else:
152            l = _to_string(self.__buf).split(' ')
153            for i, s in enumerate(l):
154                if i == 0:
155                    self.__opc = [ord(x) for x in s]
156                elif s:
157                    self.__arg.append(s)
158
159    def process_incoming(self, x):
160        if _is_null(self.__buf) and not self.__scan_amp(x):
161            return None, None, None, None, None, None, -1
162
163        typ = _get_type(self.__buf)
164        if typ == _null:
165            ret = self.__scan_null(x)
166        elif typ == _fast:
167            ret = self.__scan_fast(x)
168        else:
169            ret = self.__scan_slow(x)
170        if not ret:
171            if typ != _fast:
172                msg = _to_string(self.__buf)
173            else:
174                msg = None
175            return None, None, None, None, None, msg, self.__pos
176
177        typ = _get_type(self.__buf)
178        if typ == _null:
179            li = self.__match_null()
180        elif typ == _fast:
181            li = self.__match_fast()
182        else:
183            li = self.__match_slow()
184
185        if self.__amp:
186            s = _to_string(self.__amp)
187            if s == '+':
188                amp = 1
189            elif s == '-':
190                amp = -1
191            else:
192                amp = int(s)
193                if amp > util.MAX_INT:
194                    amp = util.MAX_INT
195                elif amp < util.MIN_INT:
196                    amp = util.MIN_INT
197        else:
198            amp = None
199        if util.isprints(self.__opc):
200            opc = _to_string(self.__opc)
201        else:
202            opc = ''
203        if _is_slow(self.__buf):
204            msg = ''
205        else:
206            msg = None
207        return li, amp, opc, self.__arg[:], self.__buf[:], msg, self.__pos
208
209    def __scan_amp(self, x):
210        if self.__amp:
211            if not _is_digit(x):
212                return True
213        elif x == ord('+') or x == ord('-'):
214            self.__amp.append(x)
215            return False
216        elif x == ord('0') or not _is_digit(x):
217            return True # 0 for literal.zero
218        self.__amp.append(x)
219        return False
220
221    def __scan_null(self, x):
222        if x == kbd.ESCAPE:
223            self.clear()
224            return True
225        elif x == kbd.MOUSE:
226            self.__set_buffer(literal.mouse.seq)
227            return True
228        elif x == kbd.RESIZE:
229            self.__set_buffer(literal.resize.seq)
230            return True
231        else:
232            self.__add_buffer(x)
233            return not _is_slow(self.__buf)
234
235    def __scan_fast(self, x):
236        if x == kbd.ESCAPE:
237            self.clear()
238            return True
239        elif x == kbd.MOUSE:
240            self.__set_buffer(literal.mouse.seq)
241            return True
242        elif x == kbd.RESIZE:
243            self.__set_buffer(literal.resize.seq)
244            return True
245        else:
246            self.__add_buffer(x)
247            return True
248
249    def __scan_slow(self, x):
250        self.__scan_slow_prev(x)
251        if x == kbd.ESCAPE:
252            self.__add_history()
253            self.__clear_candidate()
254            self.clear()
255            return True
256        elif x == kbd.MOUSE:
257            self.__set_buffer(literal.mouse.seq)
258            return True
259        elif x == kbd.RESIZE:
260            self.__set_buffer(literal.resize.seq)
261            return True
262        elif x == kbd.TAB:
263            return self.__scan_slow_tab()
264        elif x in (kbd.UP, util.ctrl('p')):
265            self.__get_older_history()
266            return False
267        elif x in (kbd.DOWN, util.ctrl('n')):
268            self.__get_newer_history()
269            return False
270        elif x == kbd.LEFT:
271            if self.__pos > self.__get_min_cursor():
272                self.__pos -= 1
273            return False
274        elif x == kbd.RIGHT:
275            if self.__pos < self.__get_tail_cursor():
276                self.__pos += 1
277            return False
278        elif x in kbd.get_backspaces():
279            self.__backspace_slow()
280            return False
281        elif x == kbd.DELETE:
282            self.__delete_slow()
283            return False
284        elif x == util.ctrl('a'):
285            self.__pos = self.__get_min_cursor()
286            return False
287        elif x == util.ctrl('e'):
288            self.__pos = self.__get_tail_cursor()
289            return False
290        elif x == util.ctrl('k'):
291            self.__buf[self.__pos:] = ''
292            self.__parse_buffer()
293            return False
294        elif x == kbd.ENTER:
295            self.__parse_buffer()
296            self.__add_history()
297            self.__clear_candidate()
298            return True
299        elif util.isprint(x):
300            if len(self.__opc) < self.__get_max_cursor():
301                self.__add_buffer(x)
302            return False
303        else:
304            return False
305
306    def __scan_slow_prev(self, x):
307        prev_key = self.__prev.key
308        if prev_key != kbd.TAB and x == kbd.TAB:
309            self.__parse_buffer()
310            self.__prev.opc = _to_string(self.__opc)
311            self.__prev.arg = self.__arg[:]
312        elif prev_key not in _arrows and x in _arrows:
313            self.__parse_buffer()
314            self.__prev.raw = self.__buf[:]
315        elif prev_key == kbd.TAB and x != kbd.TAB:
316            self.__prev.opc = ''
317            self.__prev.arg = ''
318        elif prev_key in _arrows and x not in _arrows:
319            self.__prev.raw = []
320        self.__prev.key = x
321
322    def __scan_slow_tab(self):
323        opc = self.__prev.opc
324        arg = self.__prev.arg
325
326        # handle a special case (ends with space) first
327        if self.__buf[self.__get_tail_cursor() - 1] == ord(' '):
328            if not arg and opc in self.__path_li_str:
329                # e.g. ":e <TAB>"
330                self.__set_string(opc + " ./")
331                self.__parse_buffer_update_arg()
332                return self.__scan_slow_tab()
333            else:
334                return False
335        # ignore unless at tail, but test this after above
336        if not self.__at_tail_cursor():
337            return False
338
339        if not arg:
340            s = self.__cand[None].get(opc)
341            if s:
342                self.__set_string(s)
343        elif len(arg) == 1 and opc in self.__cand:
344            cand = self.__cand[opc]
345            if isinstance(cand, candidate.PathCandidate):
346                l = cand.get(arg[0])
347                if l is not None:
348                    s, n = l
349                    if s:
350                        self.__set_string(opc + " " + s)
351                        if n == 1:
352                            # This is the only candidate, so update buffer for
353                            # the next iteration which may need to start from
354                            # beginning with a different input (e.g. change from
355                            # a dir path without trailing / to with trailing /).
356                            self.__parse_buffer_update_arg()
357            else:
358                s = cand.get(arg[0])
359                if s:
360                    self.__set_string(opc + " " + s)
361        return False
362
363    # XXX needs refactoring, should avoid updating __prev here
364    def __parse_buffer_update_arg(self):
365        self.__parse_buffer()
366        self.__prev.arg = self.__arg[:]
367
368    def __delete_slow(self):
369        key = chr(self.__buf[0])
370        if self.__at_tail_cursor():
371            self.__backspace_slow()
372        else:
373            del self.__buf[self.__pos]
374        if not self.__buf:
375            self.clear()
376            self.__history.reset_cursor(key)
377        self.__parse_buffer()
378
379    def __backspace_slow(self):
380        key = chr(self.__buf[0])
381        if self.__at_min_cursor() and not self.__at_tail_cursor():
382            return
383        del self.__buf[self.__pos - 1]
384        self.__pos -= 1
385        if not self.__buf:
386            self.clear()
387            self.__history.reset_cursor(key)
388        self.__parse_buffer()
389
390    def __match_null(self):
391        return literal.escape
392
393    def __match_fast(self):
394        beg = 0
395        end = len(self.__fasts) - 1
396        seq = tuple(self.__opc)
397        while True:
398            i = (beg + end) // 2
399            o = self.__fasts[i]
400            if o.match(seq):
401                return o
402            elif o.match_incomplete(seq):
403                return None
404            if seq < o.seq:
405                end = i - 1
406            else:
407                beg = i + 1
408            if beg > end:
409                break
410
411        for o in self.__regxs:
412            if o.match(seq):
413                return o
414            elif o.match_incomplete(seq):
415                return None
416        self.clear()
417        return None
418
419    def __match_slow(self):
420        l = []
421        for o in self.__slows:
422            if o.match(self.__opc):
423                return o
424            elif o.match_incomplete(self.__opc):
425                l.append(o)
426        if len(l) == 1:
427            s = l[0].str + " " + ' '.join(self.__arg)
428            self.__set_string(s.rstrip())
429            return l[0]
430        else:
431            s = _to_string(self.__buf)
432            return literal.InvalidLiteral(s, None, '')
433
434_arrows = list(kbd.get_arrows())
435_arrows.append(util.ctrl('p'))
436_arrows.append(util.ctrl('n'))
437_null, _fast, _slow = range(3)
438
439def _to_string(l):
440    return ''.join([chr(x) for x in l])
441
442def _is_digit(x):
443    return ord('0') <= x <= ord('9')
444
445def _is_null(l):
446    return _get_type(l) == _null
447
448def _is_slow(l):
449    return _get_type(l) == _slow
450
451def _get_type(l):
452    if not l:
453        return _null
454    elif l[0] in literal.get_slow_ords():
455        return _slow
456    else:
457        return _fast
458
459_li_cand = candidate.LiteralCandidate()
460_arg_cand = candidate.LiteralCandidate()
461_path_cand = candidate.PathCandidate()
462