17db96d56Sopenharmony_ci"""Complete either attribute names or file names. 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ciEither on demand or after a user-selected delay after a key character, 47db96d56Sopenharmony_cipop up a list of candidates. 57db96d56Sopenharmony_ci""" 67db96d56Sopenharmony_ciimport __main__ 77db96d56Sopenharmony_ciimport keyword 87db96d56Sopenharmony_ciimport os 97db96d56Sopenharmony_ciimport string 107db96d56Sopenharmony_ciimport sys 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ci# Modified keyword list is used in fetch_completions. 137db96d56Sopenharmony_cicompletion_kwds = [s for s in keyword.kwlist 147db96d56Sopenharmony_ci if s not in {'True', 'False', 'None'}] # In builtins. 157db96d56Sopenharmony_cicompletion_kwds.extend(('match', 'case')) # Context keywords. 167db96d56Sopenharmony_cicompletion_kwds.sort() 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_ci# Two types of completions; defined here for autocomplete_w import below. 197db96d56Sopenharmony_ciATTRS, FILES = 0, 1 207db96d56Sopenharmony_cifrom idlelib import autocomplete_w 217db96d56Sopenharmony_cifrom idlelib.config import idleConf 227db96d56Sopenharmony_cifrom idlelib.hyperparser import HyperParser 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_ci# Tuples passed to open_completions. 257db96d56Sopenharmony_ci# EvalFunc, Complete, WantWin, Mode 267db96d56Sopenharmony_ciFORCE = True, False, True, None # Control-Space. 277db96d56Sopenharmony_ciTAB = False, True, True, None # Tab. 287db96d56Sopenharmony_ciTRY_A = False, False, False, ATTRS # '.' for attributes. 297db96d56Sopenharmony_ciTRY_F = False, False, False, FILES # '/' in quotes for file name. 307db96d56Sopenharmony_ci 317db96d56Sopenharmony_ci# This string includes all chars that may be in an identifier. 327db96d56Sopenharmony_ci# TODO Update this here and elsewhere. 337db96d56Sopenharmony_ciID_CHARS = string.ascii_letters + string.digits + "_" 347db96d56Sopenharmony_ci 357db96d56Sopenharmony_ciSEPS = f"{os.sep}{os.altsep if os.altsep else ''}" 367db96d56Sopenharmony_ciTRIGGERS = f".{SEPS}" 377db96d56Sopenharmony_ci 387db96d56Sopenharmony_ciclass AutoComplete: 397db96d56Sopenharmony_ci 407db96d56Sopenharmony_ci def __init__(self, editwin=None, tags=None): 417db96d56Sopenharmony_ci self.editwin = editwin 427db96d56Sopenharmony_ci if editwin is not None: # not in subprocess or no-gui test 437db96d56Sopenharmony_ci self.text = editwin.text 447db96d56Sopenharmony_ci self.tags = tags 457db96d56Sopenharmony_ci self.autocompletewindow = None 467db96d56Sopenharmony_ci # id of delayed call, and the index of the text insert when 477db96d56Sopenharmony_ci # the delayed call was issued. If _delayed_completion_id is 487db96d56Sopenharmony_ci # None, there is no delayed call. 497db96d56Sopenharmony_ci self._delayed_completion_id = None 507db96d56Sopenharmony_ci self._delayed_completion_index = None 517db96d56Sopenharmony_ci 527db96d56Sopenharmony_ci @classmethod 537db96d56Sopenharmony_ci def reload(cls): 547db96d56Sopenharmony_ci cls.popupwait = idleConf.GetOption( 557db96d56Sopenharmony_ci "extensions", "AutoComplete", "popupwait", type="int", default=0) 567db96d56Sopenharmony_ci 577db96d56Sopenharmony_ci def _make_autocomplete_window(self): # Makes mocking easier. 587db96d56Sopenharmony_ci return autocomplete_w.AutoCompleteWindow(self.text, tags=self.tags) 597db96d56Sopenharmony_ci 607db96d56Sopenharmony_ci def _remove_autocomplete_window(self, event=None): 617db96d56Sopenharmony_ci if self.autocompletewindow: 627db96d56Sopenharmony_ci self.autocompletewindow.hide_window() 637db96d56Sopenharmony_ci self.autocompletewindow = None 647db96d56Sopenharmony_ci 657db96d56Sopenharmony_ci def force_open_completions_event(self, event): 667db96d56Sopenharmony_ci "(^space) Open completion list, even if a function call is needed." 677db96d56Sopenharmony_ci self.open_completions(FORCE) 687db96d56Sopenharmony_ci return "break" 697db96d56Sopenharmony_ci 707db96d56Sopenharmony_ci def autocomplete_event(self, event): 717db96d56Sopenharmony_ci "(tab) Complete word or open list if multiple options." 727db96d56Sopenharmony_ci if hasattr(event, "mc_state") and event.mc_state or\ 737db96d56Sopenharmony_ci not self.text.get("insert linestart", "insert").strip(): 747db96d56Sopenharmony_ci # A modifier was pressed along with the tab or 757db96d56Sopenharmony_ci # there is only previous whitespace on this line, so tab. 767db96d56Sopenharmony_ci return None 777db96d56Sopenharmony_ci if self.autocompletewindow and self.autocompletewindow.is_active(): 787db96d56Sopenharmony_ci self.autocompletewindow.complete() 797db96d56Sopenharmony_ci return "break" 807db96d56Sopenharmony_ci else: 817db96d56Sopenharmony_ci opened = self.open_completions(TAB) 827db96d56Sopenharmony_ci return "break" if opened else None 837db96d56Sopenharmony_ci 847db96d56Sopenharmony_ci def try_open_completions_event(self, event=None): 857db96d56Sopenharmony_ci "(./) Open completion list after pause with no movement." 867db96d56Sopenharmony_ci lastchar = self.text.get("insert-1c") 877db96d56Sopenharmony_ci if lastchar in TRIGGERS: 887db96d56Sopenharmony_ci args = TRY_A if lastchar == "." else TRY_F 897db96d56Sopenharmony_ci self._delayed_completion_index = self.text.index("insert") 907db96d56Sopenharmony_ci if self._delayed_completion_id is not None: 917db96d56Sopenharmony_ci self.text.after_cancel(self._delayed_completion_id) 927db96d56Sopenharmony_ci self._delayed_completion_id = self.text.after( 937db96d56Sopenharmony_ci self.popupwait, self._delayed_open_completions, args) 947db96d56Sopenharmony_ci 957db96d56Sopenharmony_ci def _delayed_open_completions(self, args): 967db96d56Sopenharmony_ci "Call open_completions if index unchanged." 977db96d56Sopenharmony_ci self._delayed_completion_id = None 987db96d56Sopenharmony_ci if self.text.index("insert") == self._delayed_completion_index: 997db96d56Sopenharmony_ci self.open_completions(args) 1007db96d56Sopenharmony_ci 1017db96d56Sopenharmony_ci def open_completions(self, args): 1027db96d56Sopenharmony_ci """Find the completions and create the AutoCompleteWindow. 1037db96d56Sopenharmony_ci Return True if successful (no syntax error or so found). 1047db96d56Sopenharmony_ci If complete is True, then if there's nothing to complete and no 1057db96d56Sopenharmony_ci start of completion, won't open completions and return False. 1067db96d56Sopenharmony_ci If mode is given, will open a completion list only in this mode. 1077db96d56Sopenharmony_ci """ 1087db96d56Sopenharmony_ci evalfuncs, complete, wantwin, mode = args 1097db96d56Sopenharmony_ci # Cancel another delayed call, if it exists. 1107db96d56Sopenharmony_ci if self._delayed_completion_id is not None: 1117db96d56Sopenharmony_ci self.text.after_cancel(self._delayed_completion_id) 1127db96d56Sopenharmony_ci self._delayed_completion_id = None 1137db96d56Sopenharmony_ci 1147db96d56Sopenharmony_ci hp = HyperParser(self.editwin, "insert") 1157db96d56Sopenharmony_ci curline = self.text.get("insert linestart", "insert") 1167db96d56Sopenharmony_ci i = j = len(curline) 1177db96d56Sopenharmony_ci if hp.is_in_string() and (not mode or mode==FILES): 1187db96d56Sopenharmony_ci # Find the beginning of the string. 1197db96d56Sopenharmony_ci # fetch_completions will look at the file system to determine 1207db96d56Sopenharmony_ci # whether the string value constitutes an actual file name 1217db96d56Sopenharmony_ci # XXX could consider raw strings here and unescape the string 1227db96d56Sopenharmony_ci # value if it's not raw. 1237db96d56Sopenharmony_ci self._remove_autocomplete_window() 1247db96d56Sopenharmony_ci mode = FILES 1257db96d56Sopenharmony_ci # Find last separator or string start 1267db96d56Sopenharmony_ci while i and curline[i-1] not in "'\"" + SEPS: 1277db96d56Sopenharmony_ci i -= 1 1287db96d56Sopenharmony_ci comp_start = curline[i:j] 1297db96d56Sopenharmony_ci j = i 1307db96d56Sopenharmony_ci # Find string start 1317db96d56Sopenharmony_ci while i and curline[i-1] not in "'\"": 1327db96d56Sopenharmony_ci i -= 1 1337db96d56Sopenharmony_ci comp_what = curline[i:j] 1347db96d56Sopenharmony_ci elif hp.is_in_code() and (not mode or mode==ATTRS): 1357db96d56Sopenharmony_ci self._remove_autocomplete_window() 1367db96d56Sopenharmony_ci mode = ATTRS 1377db96d56Sopenharmony_ci while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127): 1387db96d56Sopenharmony_ci i -= 1 1397db96d56Sopenharmony_ci comp_start = curline[i:j] 1407db96d56Sopenharmony_ci if i and curline[i-1] == '.': # Need object with attributes. 1417db96d56Sopenharmony_ci hp.set_index("insert-%dc" % (len(curline)-(i-1))) 1427db96d56Sopenharmony_ci comp_what = hp.get_expression() 1437db96d56Sopenharmony_ci if (not comp_what or 1447db96d56Sopenharmony_ci (not evalfuncs and comp_what.find('(') != -1)): 1457db96d56Sopenharmony_ci return None 1467db96d56Sopenharmony_ci else: 1477db96d56Sopenharmony_ci comp_what = "" 1487db96d56Sopenharmony_ci else: 1497db96d56Sopenharmony_ci return None 1507db96d56Sopenharmony_ci 1517db96d56Sopenharmony_ci if complete and not comp_what and not comp_start: 1527db96d56Sopenharmony_ci return None 1537db96d56Sopenharmony_ci comp_lists = self.fetch_completions(comp_what, mode) 1547db96d56Sopenharmony_ci if not comp_lists[0]: 1557db96d56Sopenharmony_ci return None 1567db96d56Sopenharmony_ci self.autocompletewindow = self._make_autocomplete_window() 1577db96d56Sopenharmony_ci return not self.autocompletewindow.show_window( 1587db96d56Sopenharmony_ci comp_lists, "insert-%dc" % len(comp_start), 1597db96d56Sopenharmony_ci complete, mode, wantwin) 1607db96d56Sopenharmony_ci 1617db96d56Sopenharmony_ci def fetch_completions(self, what, mode): 1627db96d56Sopenharmony_ci """Return a pair of lists of completions for something. The first list 1637db96d56Sopenharmony_ci is a sublist of the second. Both are sorted. 1647db96d56Sopenharmony_ci 1657db96d56Sopenharmony_ci If there is a Python subprocess, get the comp. list there. Otherwise, 1667db96d56Sopenharmony_ci either fetch_completions() is running in the subprocess itself or it 1677db96d56Sopenharmony_ci was called in an IDLE EditorWindow before any script had been run. 1687db96d56Sopenharmony_ci 1697db96d56Sopenharmony_ci The subprocess environment is that of the most recently run script. If 1707db96d56Sopenharmony_ci two unrelated modules are being edited some calltips in the current 1717db96d56Sopenharmony_ci module may be inoperative if the module was not the last to run. 1727db96d56Sopenharmony_ci """ 1737db96d56Sopenharmony_ci try: 1747db96d56Sopenharmony_ci rpcclt = self.editwin.flist.pyshell.interp.rpcclt 1757db96d56Sopenharmony_ci except: 1767db96d56Sopenharmony_ci rpcclt = None 1777db96d56Sopenharmony_ci if rpcclt: 1787db96d56Sopenharmony_ci return rpcclt.remotecall("exec", "get_the_completion_list", 1797db96d56Sopenharmony_ci (what, mode), {}) 1807db96d56Sopenharmony_ci else: 1817db96d56Sopenharmony_ci if mode == ATTRS: 1827db96d56Sopenharmony_ci if what == "": # Main module names. 1837db96d56Sopenharmony_ci namespace = {**__main__.__builtins__.__dict__, 1847db96d56Sopenharmony_ci **__main__.__dict__} 1857db96d56Sopenharmony_ci bigl = eval("dir()", namespace) 1867db96d56Sopenharmony_ci bigl.extend(completion_kwds) 1877db96d56Sopenharmony_ci bigl.sort() 1887db96d56Sopenharmony_ci if "__all__" in bigl: 1897db96d56Sopenharmony_ci smalll = sorted(eval("__all__", namespace)) 1907db96d56Sopenharmony_ci else: 1917db96d56Sopenharmony_ci smalll = [s for s in bigl if s[:1] != '_'] 1927db96d56Sopenharmony_ci else: 1937db96d56Sopenharmony_ci try: 1947db96d56Sopenharmony_ci entity = self.get_entity(what) 1957db96d56Sopenharmony_ci bigl = dir(entity) 1967db96d56Sopenharmony_ci bigl.sort() 1977db96d56Sopenharmony_ci if "__all__" in bigl: 1987db96d56Sopenharmony_ci smalll = sorted(entity.__all__) 1997db96d56Sopenharmony_ci else: 2007db96d56Sopenharmony_ci smalll = [s for s in bigl if s[:1] != '_'] 2017db96d56Sopenharmony_ci except: 2027db96d56Sopenharmony_ci return [], [] 2037db96d56Sopenharmony_ci 2047db96d56Sopenharmony_ci elif mode == FILES: 2057db96d56Sopenharmony_ci if what == "": 2067db96d56Sopenharmony_ci what = "." 2077db96d56Sopenharmony_ci try: 2087db96d56Sopenharmony_ci expandedpath = os.path.expanduser(what) 2097db96d56Sopenharmony_ci bigl = os.listdir(expandedpath) 2107db96d56Sopenharmony_ci bigl.sort() 2117db96d56Sopenharmony_ci smalll = [s for s in bigl if s[:1] != '.'] 2127db96d56Sopenharmony_ci except OSError: 2137db96d56Sopenharmony_ci return [], [] 2147db96d56Sopenharmony_ci 2157db96d56Sopenharmony_ci if not smalll: 2167db96d56Sopenharmony_ci smalll = bigl 2177db96d56Sopenharmony_ci return smalll, bigl 2187db96d56Sopenharmony_ci 2197db96d56Sopenharmony_ci def get_entity(self, name): 2207db96d56Sopenharmony_ci "Lookup name in a namespace spanning sys.modules and __main.dict__." 2217db96d56Sopenharmony_ci return eval(name, {**sys.modules, **__main__.__dict__}) 2227db96d56Sopenharmony_ci 2237db96d56Sopenharmony_ci 2247db96d56Sopenharmony_ciAutoComplete.reload() 2257db96d56Sopenharmony_ci 2267db96d56Sopenharmony_ciif __name__ == '__main__': 2277db96d56Sopenharmony_ci from unittest import main 2287db96d56Sopenharmony_ci main('idlelib.idle_test.test_autocomplete', verbosity=2) 229