Home | History | Annotate | Download | only in idlelib
      1 """Complete either attribute names or file names.
      2 
      3 Either on demand or after a user-selected delay after a key character,
      4 pop up a list of candidates.
      5 """
      6 import os
      7 import string
      8 import sys
      9 
     10 # These constants represent the two different types of completions.
     11 # They must be defined here so autocomple_w can import them.
     12 COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1)
     13 
     14 from idlelib import autocomplete_w
     15 from idlelib.config import idleConf
     16 from idlelib.hyperparser import HyperParser
     17 import __main__
     18 
     19 # This string includes all chars that may be in an identifier.
     20 # TODO Update this here and elsewhere.
     21 ID_CHARS = string.ascii_letters + string.digits + "_"
     22 
     23 SEPS = os.sep
     24 if os.altsep:  # e.g. '/' on Windows...
     25     SEPS += os.altsep
     26 
     27 
     28 class AutoComplete:
     29 
     30     def __init__(self, editwin=None):
     31         self.editwin = editwin
     32         if editwin is not None:   # not in subprocess or test
     33             self.text = editwin.text
     34             self.autocompletewindow = None
     35             # id of delayed call, and the index of the text insert when
     36             # the delayed call was issued. If _delayed_completion_id is
     37             # None, there is no delayed call.
     38             self._delayed_completion_id = None
     39             self._delayed_completion_index = None
     40 
     41     @classmethod
     42     def reload(cls):
     43         cls.popupwait = idleConf.GetOption(
     44             "extensions", "AutoComplete", "popupwait", type="int", default=0)
     45 
     46     def _make_autocomplete_window(self):
     47         return autocomplete_w.AutoCompleteWindow(self.text)
     48 
     49     def _remove_autocomplete_window(self, event=None):
     50         if self.autocompletewindow:
     51             self.autocompletewindow.hide_window()
     52             self.autocompletewindow = None
     53 
     54     def force_open_completions_event(self, event):
     55         """Happens when the user really wants to open a completion list, even
     56         if a function call is needed.
     57         """
     58         self.open_completions(True, False, True)
     59         return "break"
     60 
     61     def try_open_completions_event(self, event):
     62         """Happens when it would be nice to open a completion list, but not
     63         really necessary, for example after a dot, so function
     64         calls won't be made.
     65         """
     66         lastchar = self.text.get("insert-1c")
     67         if lastchar == ".":
     68             self._open_completions_later(False, False, False,
     69                                          COMPLETE_ATTRIBUTES)
     70         elif lastchar in SEPS:
     71             self._open_completions_later(False, False, False,
     72                                          COMPLETE_FILES)
     73 
     74     def autocomplete_event(self, event):
     75         """Happens when the user wants to complete his word, and if necessary,
     76         open a completion list after that (if there is more than one
     77         completion)
     78         """
     79         if hasattr(event, "mc_state") and event.mc_state or\
     80                 not self.text.get("insert linestart", "insert").strip():
     81             # A modifier was pressed along with the tab or
     82             # there is only previous whitespace on this line, so tab.
     83             return None
     84         if self.autocompletewindow and self.autocompletewindow.is_active():
     85             self.autocompletewindow.complete()
     86             return "break"
     87         else:
     88             opened = self.open_completions(False, True, True)
     89             return "break" if opened else None
     90 
     91     def _open_completions_later(self, *args):
     92         self._delayed_completion_index = self.text.index("insert")
     93         if self._delayed_completion_id is not None:
     94             self.text.after_cancel(self._delayed_completion_id)
     95         self._delayed_completion_id = \
     96             self.text.after(self.popupwait, self._delayed_open_completions,
     97                             *args)
     98 
     99     def _delayed_open_completions(self, *args):
    100         self._delayed_completion_id = None
    101         if self.text.index("insert") == self._delayed_completion_index:
    102             self.open_completions(*args)
    103 
    104     def open_completions(self, evalfuncs, complete, userWantsWin, mode=None):
    105         """Find the completions and create the AutoCompleteWindow.
    106         Return True if successful (no syntax error or so found).
    107         if complete is True, then if there's nothing to complete and no
    108         start of completion, won't open completions and return False.
    109         If mode is given, will open a completion list only in this mode.
    110         """
    111         # Cancel another delayed call, if it exists.
    112         if self._delayed_completion_id is not None:
    113             self.text.after_cancel(self._delayed_completion_id)
    114             self._delayed_completion_id = None
    115 
    116         hp = HyperParser(self.editwin, "insert")
    117         curline = self.text.get("insert linestart", "insert")
    118         i = j = len(curline)
    119         if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
    120             # Find the beginning of the string
    121             # fetch_completions will look at the file system to determine whether the
    122             # string value constitutes an actual file name
    123             # XXX could consider raw strings here and unescape the string value if it's
    124             # not raw.
    125             self._remove_autocomplete_window()
    126             mode = COMPLETE_FILES
    127             # Find last separator or string start
    128             while i and curline[i-1] not in "'\"" + SEPS:
    129                 i -= 1
    130             comp_start = curline[i:j]
    131             j = i
    132             # Find string start
    133             while i and curline[i-1] not in "'\"":
    134                 i -= 1
    135             comp_what = curline[i:j]
    136         elif hp.is_in_code() and (not mode or mode==COMPLETE_ATTRIBUTES):
    137             self._remove_autocomplete_window()
    138             mode = COMPLETE_ATTRIBUTES
    139             while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127):
    140                 i -= 1
    141             comp_start = curline[i:j]
    142             if i and curline[i-1] == '.':
    143                 hp.set_index("insert-%dc" % (len(curline)-(i-1)))
    144                 comp_what = hp.get_expression()
    145                 if not comp_what or \
    146                    (not evalfuncs and comp_what.find('(') != -1):
    147                     return None
    148             else:
    149                 comp_what = ""
    150         else:
    151             return None
    152 
    153         if complete and not comp_what and not comp_start:
    154             return None
    155         comp_lists = self.fetch_completions(comp_what, mode)
    156         if not comp_lists[0]:
    157             return None
    158         self.autocompletewindow = self._make_autocomplete_window()
    159         return not self.autocompletewindow.show_window(
    160                 comp_lists, "insert-%dc" % len(comp_start),
    161                 complete, mode, userWantsWin)
    162 
    163     def fetch_completions(self, what, mode):
    164         """Return a pair of lists of completions for something. The first list
    165         is a sublist of the second. Both are sorted.
    166 
    167         If there is a Python subprocess, get the comp. list there.  Otherwise,
    168         either fetch_completions() is running in the subprocess itself or it
    169         was called in an IDLE EditorWindow before any script had been run.
    170 
    171         The subprocess environment is that of the most recently run script.  If
    172         two unrelated modules are being edited some calltips in the current
    173         module may be inoperative if the module was not the last to run.
    174         """
    175         try:
    176             rpcclt = self.editwin.flist.pyshell.interp.rpcclt
    177         except:
    178             rpcclt = None
    179         if rpcclt:
    180             return rpcclt.remotecall("exec", "get_the_completion_list",
    181                                      (what, mode), {})
    182         else:
    183             if mode == COMPLETE_ATTRIBUTES:
    184                 if what == "":
    185                     namespace = __main__.__dict__.copy()
    186                     namespace.update(__main__.__builtins__.__dict__)
    187                     bigl = eval("dir()", namespace)
    188                     bigl.sort()
    189                     if "__all__" in bigl:
    190                         smalll = sorted(eval("__all__", namespace))
    191                     else:
    192                         smalll = [s for s in bigl if s[:1] != '_']
    193                 else:
    194                     try:
    195                         entity = self.get_entity(what)
    196                         bigl = dir(entity)
    197                         bigl.sort()
    198                         if "__all__" in bigl:
    199                             smalll = sorted(entity.__all__)
    200                         else:
    201                             smalll = [s for s in bigl if s[:1] != '_']
    202                     except:
    203                         return [], []
    204 
    205             elif mode == COMPLETE_FILES:
    206                 if what == "":
    207                     what = "."
    208                 try:
    209                     expandedpath = os.path.expanduser(what)
    210                     bigl = os.listdir(expandedpath)
    211                     bigl.sort()
    212                     smalll = [s for s in bigl if s[:1] != '.']
    213                 except OSError:
    214                     return [], []
    215 
    216             if not smalll:
    217                 smalll = bigl
    218             return smalll, bigl
    219 
    220     def get_entity(self, name):
    221         """Lookup name in a namespace spanning sys.modules and __main.dict__"""
    222         namespace = sys.modules.copy()
    223         namespace.update(__main__.__dict__)
    224         return eval(name, namespace)
    225 
    226 
    227 AutoComplete.reload()
    228 
    229 if __name__ == '__main__':
    230     from unittest import main
    231     main('idlelib.idle_test.test_autocomplete', verbosity=2)
    232