blob: 032d31225315fb71a8efcbbc11b344f83ba1ea7c [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__
Terry Jan Reedybce2eb42020-07-09 18:08:33 -04007import 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
Miss Islington (bot)a2947032021-10-16 16:14:11 -070012# Modified keyword list is used in fetch_completions.
13completion_kwds = [s for s in keyword.kwlist
14 if s not in {'True', 'False', 'None'}] # In builtins.
15completion_kwds.extend(('match', 'case')) # Context keywords.
16completion_kwds.sort()
17
Terry Jan Reedy12131232019-08-04 19:48:52 -040018# Two types of completions; defined here for autocomplete_w import below.
19ATTRS, FILES = 0, 1
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040020from idlelib import autocomplete_w
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040021from idlelib.config import idleConf
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040022from idlelib.hyperparser import HyperParser
Kurt B. Kaisere1b4a162007-08-10 02:45:06 +000023
Terry Jan Reedy12131232019-08-04 19:48:52 -040024# Tuples passed to open_completions.
25# EvalFunc, Complete, WantWin, Mode
26FORCE = True, False, True, None # Control-Space.
27TAB = False, True, True, None # Tab.
28TRY_A = False, False, False, ATTRS # '.' for attributes.
29TRY_F = False, False, False, FILES # '/' in quotes for file name.
30
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040031# This string includes all chars that may be in an identifier.
32# TODO Update this here and elsewhere.
33ID_CHARS = string.ascii_letters + string.digits + "_"
34
Terry Jan Reedy12131232019-08-04 19:48:52 -040035SEPS = f"{os.sep}{os.altsep if os.altsep else ''}"
36TRIGGERS = f".{SEPS}"
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040037
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000038class AutoComplete:
39
Tal Einatb43cc312021-05-03 05:27:38 +030040 def __init__(self, editwin=None, tags=None):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000041 self.editwin = editwin
Terry Jan Reedy12131232019-08-04 19:48:52 -040042 if editwin is not None: # not in subprocess or no-gui test
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040043 self.text = editwin.text
Tal Einatb43cc312021-05-03 05:27:38 +030044 self.tags = tags
Terry Jan Reedy12131232019-08-04 19:48:52 -040045 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
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000051
wohlganger58fc71c2017-09-10 16:19:47 -050052 @classmethod
53 def reload(cls):
54 cls.popupwait = idleConf.GetOption(
55 "extensions", "AutoComplete", "popupwait", type="int", default=0)
56
Terry Jan Reedy12131232019-08-04 19:48:52 -040057 def _make_autocomplete_window(self): # Makes mocking easier.
Tal Einatb43cc312021-05-03 05:27:38 +030058 return autocomplete_w.AutoCompleteWindow(self.text, tags=self.tags)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000059
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):
Terry Jan Reedy12131232019-08-04 19:48:52 -040066 "(^space) Open completion list, even if a function call is needed."
67 self.open_completions(FORCE)
Serhiy Storchaka213ce122017-06-27 07:02:32 +030068 return "break"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000069
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000070 def autocomplete_event(self, event):
Terry Jan Reedy12131232019-08-04 19:48:52 -040071 "(tab) Complete word or open list if multiple options."
Terry Jan Reedyc665dfd2016-07-24 23:01:28 -040072 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.
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040076 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000077 if self.autocompletewindow and self.autocompletewindow.is_active():
78 self.autocompletewindow.complete()
79 return "break"
80 else:
Terry Jan Reedy12131232019-08-04 19:48:52 -040081 opened = self.open_completions(TAB)
Terry Jan Reedyc665dfd2016-07-24 23:01:28 -040082 return "break" if opened else None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000083
Terry Jan Reedy12131232019-08-04 19:48:52 -040084 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)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000094
Terry Jan Reedy12131232019-08-04 19:48:52 -040095 def _delayed_open_completions(self, args):
96 "Call open_completions if index unchanged."
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000097 self._delayed_completion_id = None
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040098 if self.text.index("insert") == self._delayed_completion_index:
Terry Jan Reedy12131232019-08-04 19:48:52 -040099 self.open_completions(args)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000100
Terry Jan Reedy12131232019-08-04 19:48:52 -0400101 def open_completions(self, args):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000102 """Find the completions and create the AutoCompleteWindow.
103 Return True if successful (no syntax error or so found).
Louie Lu113d7352019-03-25 07:33:12 +0800104 If complete is True, then if there's nothing to complete and no
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000105 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 """
Terry Jan Reedy12131232019-08-04 19:48:52 -0400108 evalfuncs, complete, wantwin, mode = args
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000109 # 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)
Terry Jan Reedy12131232019-08-04 19:48:52 -0400117 if hp.is_in_string() and (not mode or mode==FILES):
Louie Lu113d7352019-03-25 07:33:12 +0800118 # 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.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000123 self._remove_autocomplete_window()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400124 mode = FILES
Martin v. Löwis862d13a2012-06-03 11:55:32 +0200125 # Find last separator or string start
126 while i and curline[i-1] not in "'\"" + SEPS:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000127 i -= 1
128 comp_start = curline[i:j]
129 j = i
Martin v. Löwis862d13a2012-06-03 11:55:32 +0200130 # Find string start
131 while i and curline[i-1] not in "'\"":
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000132 i -= 1
133 comp_what = curline[i:j]
Terry Jan Reedy12131232019-08-04 19:48:52 -0400134 elif hp.is_in_code() and (not mode or mode==ATTRS):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000135 self._remove_autocomplete_window()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400136 mode = ATTRS
Martin v. Löwis993fe3f2012-06-14 15:37:21 +0200137 while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000138 i -= 1
139 comp_start = curline[i:j]
Terry Jan Reedy12131232019-08-04 19:48:52 -0400140 if i and curline[i-1] == '.': # Need object with attributes.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000141 hp.set_index("insert-%dc" % (len(curline)-(i-1)))
142 comp_what = hp.get_expression()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400143 if (not comp_what or
144 (not evalfuncs and comp_what.find('(') != -1)):
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400145 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000146 else:
147 comp_what = ""
148 else:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400149 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000150
151 if complete and not comp_what and not comp_start:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400152 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000153 comp_lists = self.fetch_completions(comp_what, mode)
154 if not comp_lists[0]:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400155 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000156 self.autocompletewindow = self._make_autocomplete_window()
Serhiy Storchakadd4754e2013-09-11 22:46:27 +0300157 return not self.autocompletewindow.show_window(
158 comp_lists, "insert-%dc" % len(comp_start),
Terry Jan Reedy12131232019-08-04 19:48:52 -0400159 complete, mode, wantwin)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000160
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:
Terry Jan Reedy12131232019-08-04 19:48:52 -0400181 if mode == ATTRS:
Terry Jan Reedybce2eb42020-07-09 18:08:33 -0400182 if what == "": # Main module names.
Terry Jan Reedy0fe45132019-03-24 17:12:28 -0400183 namespace = {**__main__.__builtins__.__dict__,
184 **__main__.__dict__}
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000185 bigl = eval("dir()", namespace)
Miss Islington (bot)a2947032021-10-16 16:14:11 -0700186 bigl.extend(completion_kwds)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000187 bigl.sort()
188 if "__all__" in bigl:
Terry Jan Reedya77aa692012-02-05 14:31:16 -0500189 smalll = sorted(eval("__all__", namespace))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000190 else:
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000191 smalll = [s for s in bigl if s[:1] != '_']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000192 else:
193 try:
194 entity = self.get_entity(what)
195 bigl = dir(entity)
196 bigl.sort()
197 if "__all__" in bigl:
Terry Jan Reedya77aa692012-02-05 14:31:16 -0500198 smalll = sorted(entity.__all__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000199 else:
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000200 smalll = [s for s in bigl if s[:1] != '_']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000201 except:
202 return [], []
203
Terry Jan Reedy12131232019-08-04 19:48:52 -0400204 elif mode == FILES:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000205 if what == "":
206 what = "."
207 try:
208 expandedpath = os.path.expanduser(what)
209 bigl = os.listdir(expandedpath)
210 bigl.sort()
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000211 smalll = [s for s in bigl if s[:1] != '.']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000212 except OSError:
213 return [], []
214
215 if not smalll:
216 smalll = bigl
217 return smalll, bigl
218
219 def get_entity(self, name):
Terry Jan Reedy0fe45132019-03-24 17:12:28 -0400220 "Lookup name in a namespace spanning sys.modules and __main.dict__."
221 return eval(name, {**sys.modules, **__main__.__dict__})
Terry Jan Reedye3fcfc22014-06-03 20:54:21 -0400222
223
wohlganger58fc71c2017-09-10 16:19:47 -0500224AutoComplete.reload()
225
Terry Jan Reedye3fcfc22014-06-03 20:54:21 -0400226if __name__ == '__main__':
227 from unittest import main
228 main('idlelib.idle_test.test_autocomplete', verbosity=2)