src/extras/pyrepl/completing_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 #
       
     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()