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