blob: e1e9e17311eda1e77d63c218dbb2eb4e2284deab [file] [log] [blame]
wohlganger58fc71c2017-09-10 16:19:47 -05001"""Complete either attribute names or file names.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00002
wohlganger58fc71c2017-09-10 16:19:47 -05003Either on demand or after a user-selected delay after a key character,
4pop up a list of candidates.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00005"""
Terry Jan Reedy0fe45132019-03-24 17:12:28 -04006import __main__
Miss Islington (bot)fd27fb72020-07-09 15:54:14 -07007import keyword
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00008import os
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00009import string
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040010import sys
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000011
Terry Jan Reedy12131232019-08-04 19:48:52 -040012# Two types of completions; defined here for autocomplete_w import below.
13ATTRS, FILES = 0, 1
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040014from idlelib import autocomplete_w
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040015from idlelib.config import idleConf
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040016from idlelib.hyperparser import HyperParser
Kurt B. Kaisere1b4a162007-08-10 02:45:06 +000017
Terry Jan Reedy12131232019-08-04 19:48:52 -040018# Tuples passed to open_completions.
19# EvalFunc, Complete, WantWin, Mode
20FORCE = True, False, True, None # Control-Space.
21TAB = False, True, True, None # Tab.
22TRY_A = False, False, False, ATTRS # '.' for attributes.
23TRY_F = False, False, False, FILES # '/' in quotes for file name.
24
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040025# This string includes all chars that may be in an identifier.
26# TODO Update this here and elsewhere.
27ID_CHARS = string.ascii_letters + string.digits + "_"
28
Terry Jan Reedy12131232019-08-04 19:48:52 -040029SEPS = f"{os.sep}{os.altsep if os.altsep else ''}"
30TRIGGERS = f".{SEPS}"
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040031
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000032class AutoComplete:
33
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000034 def __init__(self, editwin=None):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000035 self.editwin = editwin
Terry Jan Reedy12131232019-08-04 19:48:52 -040036 if editwin is not None: # not in subprocess or no-gui test
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040037 self.text = editwin.text
Terry Jan Reedy12131232019-08-04 19:48:52 -040038 self.autocompletewindow = None
39 # id of delayed call, and the index of the text insert when
40 # the delayed call was issued. If _delayed_completion_id is
41 # None, there is no delayed call.
42 self._delayed_completion_id = None
43 self._delayed_completion_index = None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000044
wohlganger58fc71c2017-09-10 16:19:47 -050045 @classmethod
46 def reload(cls):
47 cls.popupwait = idleConf.GetOption(
48 "extensions", "AutoComplete", "popupwait", type="int", default=0)
49
Terry Jan Reedy12131232019-08-04 19:48:52 -040050 def _make_autocomplete_window(self): # Makes mocking easier.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040051 return autocomplete_w.AutoCompleteWindow(self.text)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000052
53 def _remove_autocomplete_window(self, event=None):
54 if self.autocompletewindow:
55 self.autocompletewindow.hide_window()
56 self.autocompletewindow = None
57
58 def force_open_completions_event(self, event):
Terry Jan Reedy12131232019-08-04 19:48:52 -040059 "(^space) Open completion list, even if a function call is needed."
60 self.open_completions(FORCE)
Serhiy Storchaka213ce122017-06-27 07:02:32 +030061 return "break"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000062
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000063 def autocomplete_event(self, event):
Terry Jan Reedy12131232019-08-04 19:48:52 -040064 "(tab) Complete word or open list if multiple options."
Terry Jan Reedyc665dfd2016-07-24 23:01:28 -040065 if hasattr(event, "mc_state") and event.mc_state or\
66 not self.text.get("insert linestart", "insert").strip():
67 # A modifier was pressed along with the tab or
68 # there is only previous whitespace on this line, so tab.
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040069 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000070 if self.autocompletewindow and self.autocompletewindow.is_active():
71 self.autocompletewindow.complete()
72 return "break"
73 else:
Terry Jan Reedy12131232019-08-04 19:48:52 -040074 opened = self.open_completions(TAB)
Terry Jan Reedyc665dfd2016-07-24 23:01:28 -040075 return "break" if opened else None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000076
Terry Jan Reedy12131232019-08-04 19:48:52 -040077 def try_open_completions_event(self, event=None):
78 "(./) Open completion list after pause with no movement."
79 lastchar = self.text.get("insert-1c")
80 if lastchar in TRIGGERS:
81 args = TRY_A if lastchar == "." else TRY_F
82 self._delayed_completion_index = self.text.index("insert")
83 if self._delayed_completion_id is not None:
84 self.text.after_cancel(self._delayed_completion_id)
85 self._delayed_completion_id = self.text.after(
86 self.popupwait, self._delayed_open_completions, args)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000087
Terry Jan Reedy12131232019-08-04 19:48:52 -040088 def _delayed_open_completions(self, args):
89 "Call open_completions if index unchanged."
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000090 self._delayed_completion_id = None
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040091 if self.text.index("insert") == self._delayed_completion_index:
Terry Jan Reedy12131232019-08-04 19:48:52 -040092 self.open_completions(args)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000093
Terry Jan Reedy12131232019-08-04 19:48:52 -040094 def open_completions(self, args):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000095 """Find the completions and create the AutoCompleteWindow.
96 Return True if successful (no syntax error or so found).
Louie Lu113d7352019-03-25 07:33:12 +080097 If complete is True, then if there's nothing to complete and no
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000098 start of completion, won't open completions and return False.
99 If mode is given, will open a completion list only in this mode.
100 """
Terry Jan Reedy12131232019-08-04 19:48:52 -0400101 evalfuncs, complete, wantwin, mode = args
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000102 # Cancel another delayed call, if it exists.
103 if self._delayed_completion_id is not None:
104 self.text.after_cancel(self._delayed_completion_id)
105 self._delayed_completion_id = None
106
107 hp = HyperParser(self.editwin, "insert")
108 curline = self.text.get("insert linestart", "insert")
109 i = j = len(curline)
Terry Jan Reedy12131232019-08-04 19:48:52 -0400110 if hp.is_in_string() and (not mode or mode==FILES):
Louie Lu113d7352019-03-25 07:33:12 +0800111 # Find the beginning of the string.
112 # fetch_completions will look at the file system to determine
113 # whether the string value constitutes an actual file name
114 # XXX could consider raw strings here and unescape the string
115 # value if it's not raw.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000116 self._remove_autocomplete_window()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400117 mode = FILES
Martin v. Löwis862d13a2012-06-03 11:55:32 +0200118 # Find last separator or string start
119 while i and curline[i-1] not in "'\"" + SEPS:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000120 i -= 1
121 comp_start = curline[i:j]
122 j = i
Martin v. Löwis862d13a2012-06-03 11:55:32 +0200123 # Find string start
124 while i and curline[i-1] not in "'\"":
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000125 i -= 1
126 comp_what = curline[i:j]
Terry Jan Reedy12131232019-08-04 19:48:52 -0400127 elif hp.is_in_code() and (not mode or mode==ATTRS):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000128 self._remove_autocomplete_window()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400129 mode = ATTRS
Martin v. Löwis993fe3f2012-06-14 15:37:21 +0200130 while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000131 i -= 1
132 comp_start = curline[i:j]
Terry Jan Reedy12131232019-08-04 19:48:52 -0400133 if i and curline[i-1] == '.': # Need object with attributes.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000134 hp.set_index("insert-%dc" % (len(curline)-(i-1)))
135 comp_what = hp.get_expression()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400136 if (not comp_what or
137 (not evalfuncs and comp_what.find('(') != -1)):
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400138 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000139 else:
140 comp_what = ""
141 else:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400142 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000143
144 if complete and not comp_what and not comp_start:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400145 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000146 comp_lists = self.fetch_completions(comp_what, mode)
147 if not comp_lists[0]:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400148 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000149 self.autocompletewindow = self._make_autocomplete_window()
Serhiy Storchakadd4754e2013-09-11 22:46:27 +0300150 return not self.autocompletewindow.show_window(
151 comp_lists, "insert-%dc" % len(comp_start),
Terry Jan Reedy12131232019-08-04 19:48:52 -0400152 complete, mode, wantwin)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000153
154 def fetch_completions(self, what, mode):
155 """Return a pair of lists of completions for something. The first list
156 is a sublist of the second. Both are sorted.
157
158 If there is a Python subprocess, get the comp. list there. Otherwise,
159 either fetch_completions() is running in the subprocess itself or it
160 was called in an IDLE EditorWindow before any script had been run.
161
162 The subprocess environment is that of the most recently run script. If
163 two unrelated modules are being edited some calltips in the current
164 module may be inoperative if the module was not the last to run.
165 """
166 try:
167 rpcclt = self.editwin.flist.pyshell.interp.rpcclt
168 except:
169 rpcclt = None
170 if rpcclt:
171 return rpcclt.remotecall("exec", "get_the_completion_list",
172 (what, mode), {})
173 else:
Terry Jan Reedy12131232019-08-04 19:48:52 -0400174 if mode == ATTRS:
Miss Islington (bot)fd27fb72020-07-09 15:54:14 -0700175 if what == "": # Main module names.
Terry Jan Reedy0fe45132019-03-24 17:12:28 -0400176 namespace = {**__main__.__builtins__.__dict__,
177 **__main__.__dict__}
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000178 bigl = eval("dir()", namespace)
Miss Islington (bot)fd27fb72020-07-09 15:54:14 -0700179 kwds = (s for s in keyword.kwlist
180 if s not in {'True', 'False', 'None'})
181 bigl.extend(kwds)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000182 bigl.sort()
183 if "__all__" in bigl:
Terry Jan Reedya77aa692012-02-05 14:31:16 -0500184 smalll = sorted(eval("__all__", namespace))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000185 else:
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000186 smalll = [s for s in bigl if s[:1] != '_']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000187 else:
188 try:
189 entity = self.get_entity(what)
190 bigl = dir(entity)
191 bigl.sort()
192 if "__all__" in bigl:
Terry Jan Reedya77aa692012-02-05 14:31:16 -0500193 smalll = sorted(entity.__all__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000194 else:
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000195 smalll = [s for s in bigl if s[:1] != '_']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000196 except:
197 return [], []
198
Terry Jan Reedy12131232019-08-04 19:48:52 -0400199 elif mode == FILES:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000200 if what == "":
201 what = "."
202 try:
203 expandedpath = os.path.expanduser(what)
204 bigl = os.listdir(expandedpath)
205 bigl.sort()
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000206 smalll = [s for s in bigl if s[:1] != '.']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000207 except OSError:
208 return [], []
209
210 if not smalll:
211 smalll = bigl
212 return smalll, bigl
213
214 def get_entity(self, name):
Terry Jan Reedy0fe45132019-03-24 17:12:28 -0400215 "Lookup name in a namespace spanning sys.modules and __main.dict__."
216 return eval(name, {**sys.modules, **__main__.__dict__})
Terry Jan Reedye3fcfc22014-06-03 20:54:21 -0400217
218
wohlganger58fc71c2017-09-10 16:19:47 -0500219AutoComplete.reload()
220
Terry Jan Reedye3fcfc22014-06-03 20:54:21 -0400221if __name__ == '__main__':
222 from unittest import main
223 main('idlelib.idle_test.test_autocomplete', verbosity=2)