blob: bb7ee035c4fefb786f199bcc9e1227818d8e63ce [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
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
Tal Einatb43cc312021-05-03 05:27:38 +030034 def __init__(self, editwin=None, tags=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
Tal Einatb43cc312021-05-03 05:27:38 +030038 self.tags = tags
Terry Jan Reedy12131232019-08-04 19:48:52 -040039 self.autocompletewindow = None
40 # id of delayed call, and the index of the text insert when
41 # the delayed call was issued. If _delayed_completion_id is
42 # None, there is no delayed call.
43 self._delayed_completion_id = None
44 self._delayed_completion_index = None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000045
wohlganger58fc71c2017-09-10 16:19:47 -050046 @classmethod
47 def reload(cls):
48 cls.popupwait = idleConf.GetOption(
49 "extensions", "AutoComplete", "popupwait", type="int", default=0)
50
Terry Jan Reedy12131232019-08-04 19:48:52 -040051 def _make_autocomplete_window(self): # Makes mocking easier.
Tal Einatb43cc312021-05-03 05:27:38 +030052 return autocomplete_w.AutoCompleteWindow(self.text, tags=self.tags)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000053
54 def _remove_autocomplete_window(self, event=None):
55 if self.autocompletewindow:
56 self.autocompletewindow.hide_window()
57 self.autocompletewindow = None
58
59 def force_open_completions_event(self, event):
Terry Jan Reedy12131232019-08-04 19:48:52 -040060 "(^space) Open completion list, even if a function call is needed."
61 self.open_completions(FORCE)
Serhiy Storchaka213ce122017-06-27 07:02:32 +030062 return "break"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000063
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000064 def autocomplete_event(self, event):
Terry Jan Reedy12131232019-08-04 19:48:52 -040065 "(tab) Complete word or open list if multiple options."
Terry Jan Reedyc665dfd2016-07-24 23:01:28 -040066 if hasattr(event, "mc_state") and event.mc_state or\
67 not self.text.get("insert linestart", "insert").strip():
68 # A modifier was pressed along with the tab or
69 # there is only previous whitespace on this line, so tab.
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040070 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000071 if self.autocompletewindow and self.autocompletewindow.is_active():
72 self.autocompletewindow.complete()
73 return "break"
74 else:
Terry Jan Reedy12131232019-08-04 19:48:52 -040075 opened = self.open_completions(TAB)
Terry Jan Reedyc665dfd2016-07-24 23:01:28 -040076 return "break" if opened else None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000077
Terry Jan Reedy12131232019-08-04 19:48:52 -040078 def try_open_completions_event(self, event=None):
79 "(./) Open completion list after pause with no movement."
80 lastchar = self.text.get("insert-1c")
81 if lastchar in TRIGGERS:
82 args = TRY_A if lastchar == "." else TRY_F
83 self._delayed_completion_index = self.text.index("insert")
84 if self._delayed_completion_id is not None:
85 self.text.after_cancel(self._delayed_completion_id)
86 self._delayed_completion_id = self.text.after(
87 self.popupwait, self._delayed_open_completions, args)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000088
Terry Jan Reedy12131232019-08-04 19:48:52 -040089 def _delayed_open_completions(self, args):
90 "Call open_completions if index unchanged."
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000091 self._delayed_completion_id = None
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040092 if self.text.index("insert") == self._delayed_completion_index:
Terry Jan Reedy12131232019-08-04 19:48:52 -040093 self.open_completions(args)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000094
Terry Jan Reedy12131232019-08-04 19:48:52 -040095 def open_completions(self, args):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000096 """Find the completions and create the AutoCompleteWindow.
97 Return True if successful (no syntax error or so found).
Louie Lu113d7352019-03-25 07:33:12 +080098 If complete is True, then if there's nothing to complete and no
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000099 start of completion, won't open completions and return False.
100 If mode is given, will open a completion list only in this mode.
101 """
Terry Jan Reedy12131232019-08-04 19:48:52 -0400102 evalfuncs, complete, wantwin, mode = args
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000103 # Cancel another delayed call, if it exists.
104 if self._delayed_completion_id is not None:
105 self.text.after_cancel(self._delayed_completion_id)
106 self._delayed_completion_id = None
107
108 hp = HyperParser(self.editwin, "insert")
109 curline = self.text.get("insert linestart", "insert")
110 i = j = len(curline)
Terry Jan Reedy12131232019-08-04 19:48:52 -0400111 if hp.is_in_string() and (not mode or mode==FILES):
Louie Lu113d7352019-03-25 07:33:12 +0800112 # Find the beginning of the string.
113 # fetch_completions will look at the file system to determine
114 # whether the string value constitutes an actual file name
115 # XXX could consider raw strings here and unescape the string
116 # value if it's not raw.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000117 self._remove_autocomplete_window()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400118 mode = FILES
Martin v. Löwis862d13a2012-06-03 11:55:32 +0200119 # Find last separator or string start
120 while i and curline[i-1] not in "'\"" + SEPS:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000121 i -= 1
122 comp_start = curline[i:j]
123 j = i
Martin v. Löwis862d13a2012-06-03 11:55:32 +0200124 # Find string start
125 while i and curline[i-1] not in "'\"":
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000126 i -= 1
127 comp_what = curline[i:j]
Terry Jan Reedy12131232019-08-04 19:48:52 -0400128 elif hp.is_in_code() and (not mode or mode==ATTRS):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000129 self._remove_autocomplete_window()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400130 mode = ATTRS
Martin v. Löwis993fe3f2012-06-14 15:37:21 +0200131 while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000132 i -= 1
133 comp_start = curline[i:j]
Terry Jan Reedy12131232019-08-04 19:48:52 -0400134 if i and curline[i-1] == '.': # Need object with attributes.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000135 hp.set_index("insert-%dc" % (len(curline)-(i-1)))
136 comp_what = hp.get_expression()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400137 if (not comp_what or
138 (not evalfuncs and comp_what.find('(') != -1)):
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400139 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000140 else:
141 comp_what = ""
142 else:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400143 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000144
145 if complete and not comp_what and not comp_start:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400146 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000147 comp_lists = self.fetch_completions(comp_what, mode)
148 if not comp_lists[0]:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400149 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000150 self.autocompletewindow = self._make_autocomplete_window()
Serhiy Storchakadd4754e2013-09-11 22:46:27 +0300151 return not self.autocompletewindow.show_window(
152 comp_lists, "insert-%dc" % len(comp_start),
Terry Jan Reedy12131232019-08-04 19:48:52 -0400153 complete, mode, wantwin)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000154
155 def fetch_completions(self, what, mode):
156 """Return a pair of lists of completions for something. The first list
157 is a sublist of the second. Both are sorted.
158
159 If there is a Python subprocess, get the comp. list there. Otherwise,
160 either fetch_completions() is running in the subprocess itself or it
161 was called in an IDLE EditorWindow before any script had been run.
162
163 The subprocess environment is that of the most recently run script. If
164 two unrelated modules are being edited some calltips in the current
165 module may be inoperative if the module was not the last to run.
166 """
167 try:
168 rpcclt = self.editwin.flist.pyshell.interp.rpcclt
169 except:
170 rpcclt = None
171 if rpcclt:
172 return rpcclt.remotecall("exec", "get_the_completion_list",
173 (what, mode), {})
174 else:
Terry Jan Reedy12131232019-08-04 19:48:52 -0400175 if mode == ATTRS:
Terry Jan Reedybce2eb42020-07-09 18:08:33 -0400176 if what == "": # Main module names.
Terry Jan Reedy0fe45132019-03-24 17:12:28 -0400177 namespace = {**__main__.__builtins__.__dict__,
178 **__main__.__dict__}
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000179 bigl = eval("dir()", namespace)
Terry Jan Reedybce2eb42020-07-09 18:08:33 -0400180 kwds = (s for s in keyword.kwlist
181 if s not in {'True', 'False', 'None'})
182 bigl.extend(kwds)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000183 bigl.sort()
184 if "__all__" in bigl:
Terry Jan Reedya77aa692012-02-05 14:31:16 -0500185 smalll = sorted(eval("__all__", namespace))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000186 else:
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000187 smalll = [s for s in bigl if s[:1] != '_']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000188 else:
189 try:
190 entity = self.get_entity(what)
191 bigl = dir(entity)
192 bigl.sort()
193 if "__all__" in bigl:
Terry Jan Reedya77aa692012-02-05 14:31:16 -0500194 smalll = sorted(entity.__all__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000195 else:
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000196 smalll = [s for s in bigl if s[:1] != '_']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000197 except:
198 return [], []
199
Terry Jan Reedy12131232019-08-04 19:48:52 -0400200 elif mode == FILES:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000201 if what == "":
202 what = "."
203 try:
204 expandedpath = os.path.expanduser(what)
205 bigl = os.listdir(expandedpath)
206 bigl.sort()
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000207 smalll = [s for s in bigl if s[:1] != '.']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000208 except OSError:
209 return [], []
210
211 if not smalll:
212 smalll = bigl
213 return smalll, bigl
214
215 def get_entity(self, name):
Terry Jan Reedy0fe45132019-03-24 17:12:28 -0400216 "Lookup name in a namespace spanning sys.modules and __main.dict__."
217 return eval(name, {**sys.modules, **__main__.__dict__})
Terry Jan Reedye3fcfc22014-06-03 20:54:21 -0400218
219
wohlganger58fc71c2017-09-10 16:19:47 -0500220AutoComplete.reload()
221
Terry Jan Reedye3fcfc22014-06-03 20:54:21 -0400222if __name__ == '__main__':
223 from unittest import main
224 main('idlelib.idle_test.test_autocomplete', verbosity=2)