src/extras/pyrepl/historical_reader.py
changeset 0 ca70ae20a155
equal deleted inserted replaced
-1:000000000000 0:ca70ae20a155
       
     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()