blob: c623d45a1534230658602db394c390f1c5e419fa [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__
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00007import os
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00008import string
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04009import sys
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000010
Terry Jan Reedy12131232019-08-04 19:48:52 -040011# Two types of completions; defined here for autocomplete_w import below.
12ATTRS, FILES = 0, 1
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040013from idlelib import autocomplete_w
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040014from idlelib.config import idleConf
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040015from idlelib.hyperparser import HyperParser
Kurt B. Kaisere1b4a162007-08-10 02:45:06 +000016
Terry Jan Reedy12131232019-08-04 19:48:52 -040017# Tuples passed to open_completions.
18# EvalFunc, Complete, WantWin, Mode
19FORCE = True, False, True, None # Control-Space.
20TAB = False, True, True, None # Tab.
21TRY_A = False, False, False, ATTRS # '.' for attributes.
22TRY_F = False, False, False, FILES # '/' in quotes for file name.
23
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040024# This string includes all chars that may be in an identifier.
25# TODO Update this here and elsewhere.
26ID_CHARS = string.ascii_letters + string.digits + "_"
27
Terry Jan Reedy12131232019-08-04 19:48:52 -040028SEPS = f"{os.sep}{os.altsep if os.altsep else ''}"
29TRIGGERS = f".{SEPS}"
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040030
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000031class AutoComplete:
32
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000033 def __init__(self, editwin=None):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000034 self.editwin = editwin
Terry Jan Reedy12131232019-08-04 19:48:52 -040035 if editwin is not None: # not in subprocess or no-gui test
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040036 self.text = editwin.text
Terry Jan Reedy12131232019-08-04 19:48:52 -040037 self.autocompletewindow = None
38 # id of delayed call, and the index of the text insert when
39 # the delayed call was issued. If _delayed_completion_id is
40 # None, there is no delayed call.
41 self._delayed_completion_id = None
42 self._delayed_completion_index = None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000043
wohlganger58fc71c2017-09-10 16:19:47 -050044 @classmethod
45 def reload(cls):
46 cls.popupwait = idleConf.GetOption(
47 "extensions", "AutoComplete", "popupwait", type="int", default=0)
48
Terry Jan Reedy12131232019-08-04 19:48:52 -040049 def _make_autocomplete_window(self): # Makes mocking easier.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040050 return autocomplete_w.AutoCompleteWindow(self.text)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000051
52 def _remove_autocomplete_window(self, event=None):
53 if self.autocompletewindow:
54 self.autocompletewindow.hide_window()
55 self.autocompletewindow = None
56
57 def force_open_completions_event(self, event):
Terry Jan Reedy12131232019-08-04 19:48:52 -040058 "(^space) Open completion list, even if a function call is needed."
59 self.open_completions(FORCE)
Serhiy Storchaka213ce122017-06-27 07:02:32 +030060 return "break"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000061
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000062 def autocomplete_event(self, event):
Terry Jan Reedy12131232019-08-04 19:48:52 -040063 "(tab) Complete word or open list if multiple options."
Terry Jan Reedyc665dfd2016-07-24 23:01:28 -040064 if hasattr(event, "mc_state") and event.mc_state or\
65 not self.text.get("insert linestart", "insert").strip():
66 # A modifier was pressed along with the tab or
67 # there is only previous whitespace on this line, so tab.
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040068 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000069 if self.autocompletewindow and self.autocompletewindow.is_active():
70 self.autocompletewindow.complete()
71 return "break"
72 else:
Terry Jan Reedy12131232019-08-04 19:48:52 -040073 opened = self.open_completions(TAB)
Terry Jan Reedyc665dfd2016-07-24 23:01:28 -040074 return "break" if opened else None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000075
Terry Jan Reedy12131232019-08-04 19:48:52 -040076 def try_open_completions_event(self, event=None):
77 "(./) Open completion list after pause with no movement."
78 lastchar = self.text.get("insert-1c")
79 if lastchar in TRIGGERS:
80 args = TRY_A if lastchar == "." else TRY_F
81 self._delayed_completion_index = self.text.index("insert")
82 if self._delayed_completion_id is not None:
83 self.text.after_cancel(self._delayed_completion_id)
84 self._delayed_completion_id = self.text.after(
85 self.popupwait, self._delayed_open_completions, args)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000086
Terry Jan Reedy12131232019-08-04 19:48:52 -040087 def _delayed_open_completions(self, args):
88 "Call open_completions if index unchanged."
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000089 self._delayed_completion_id = None
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -040090 if self.text.index("insert") == self._delayed_completion_index:
Terry Jan Reedy12131232019-08-04 19:48:52 -040091 self.open_completions(args)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000092
Terry Jan Reedy12131232019-08-04 19:48:52 -040093 def open_completions(self, args):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000094 """Find the completions and create the AutoCompleteWindow.
95 Return True if successful (no syntax error or so found).
Louie Lu113d7352019-03-25 07:33:12 +080096 If complete is True, then if there's nothing to complete and no
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000097 start of completion, won't open completions and return False.
98 If mode is given, will open a completion list only in this mode.
99 """
Terry Jan Reedy12131232019-08-04 19:48:52 -0400100 evalfuncs, complete, wantwin, mode = args
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000101 # Cancel another delayed call, if it exists.
102 if self._delayed_completion_id is not None:
103 self.text.after_cancel(self._delayed_completion_id)
104 self._delayed_completion_id = None
105
106 hp = HyperParser(self.editwin, "insert")
107 curline = self.text.get("insert linestart", "insert")
108 i = j = len(curline)
Terry Jan Reedy12131232019-08-04 19:48:52 -0400109 if hp.is_in_string() and (not mode or mode==FILES):
Louie Lu113d7352019-03-25 07:33:12 +0800110 # Find the beginning of the string.
111 # fetch_completions will look at the file system to determine
112 # whether the string value constitutes an actual file name
113 # XXX could consider raw strings here and unescape the string
114 # value if it's not raw.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000115 self._remove_autocomplete_window()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400116 mode = FILES
Martin v. Löwis862d13a2012-06-03 11:55:32 +0200117 # Find last separator or string start
118 while i and curline[i-1] not in "'\"" + SEPS:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000119 i -= 1
120 comp_start = curline[i:j]
121 j = i
Martin v. Löwis862d13a2012-06-03 11:55:32 +0200122 # Find string start
123 while i and curline[i-1] not in "'\"":
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000124 i -= 1
125 comp_what = curline[i:j]
Terry Jan Reedy12131232019-08-04 19:48:52 -0400126 elif hp.is_in_code() and (not mode or mode==ATTRS):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000127 self._remove_autocomplete_window()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400128 mode = ATTRS
Martin v. Löwis993fe3f2012-06-14 15:37:21 +0200129 while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000130 i -= 1
131 comp_start = curline[i:j]
Terry Jan Reedy12131232019-08-04 19:48:52 -0400132 if i and curline[i-1] == '.': # Need object with attributes.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000133 hp.set_index("insert-%dc" % (len(curline)-(i-1)))
134 comp_what = hp.get_expression()
Terry Jan Reedy12131232019-08-04 19:48:52 -0400135 if (not comp_what or
136 (not evalfuncs and comp_what.find('(') != -1)):
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400137 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000138 else:
139 comp_what = ""
140 else:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400141 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000142
143 if complete and not comp_what and not comp_start:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400144 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000145 comp_lists = self.fetch_completions(comp_what, mode)
146 if not comp_lists[0]:
Terry Jan Reedyc74fb9c2016-07-24 20:35:43 -0400147 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000148 self.autocompletewindow = self._make_autocomplete_window()
Serhiy Storchakadd4754e2013-09-11 22:46:27 +0300149 return not self.autocompletewindow.show_window(
150 comp_lists, "insert-%dc" % len(comp_start),
Terry Jan Reedy12131232019-08-04 19:48:52 -0400151 complete, mode, wantwin)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000152
153 def fetch_completions(self, what, mode):
154 """Return a pair of lists of completions for something. The first list
155 is a sublist of the second. Both are sorted.
156
157 If there is a Python subprocess, get the comp. list there. Otherwise,
158 either fetch_completions() is running in the subprocess itself or it
159 was called in an IDLE EditorWindow before any script had been run.
160
161 The subprocess environment is that of the most recently run script. If
162 two unrelated modules are being edited some calltips in the current
163 module may be inoperative if the module was not the last to run.
164 """
165 try:
166 rpcclt = self.editwin.flist.pyshell.interp.rpcclt
167 except:
168 rpcclt = None
169 if rpcclt:
170 return rpcclt.remotecall("exec", "get_the_completion_list",
171 (what, mode), {})
172 else:
Terry Jan Reedy12131232019-08-04 19:48:52 -0400173 if mode == ATTRS:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000174 if what == "":
Terry Jan Reedy0fe45132019-03-24 17:12:28 -0400175 namespace = {**__main__.__builtins__.__dict__,
176 **__main__.__dict__}
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000177 bigl = eval("dir()", namespace)
178 bigl.sort()
179 if "__all__" in bigl:
Terry Jan Reedya77aa692012-02-05 14:31:16 -0500180 smalll = sorted(eval("__all__", namespace))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000181 else:
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000182 smalll = [s for s in bigl if s[:1] != '_']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000183 else:
184 try:
185 entity = self.get_entity(what)
186 bigl = dir(entity)
187 bigl.sort()
188 if "__all__" in bigl:
Terry Jan Reedya77aa692012-02-05 14:31:16 -0500189 smalll = sorted(entity.__all__)
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 except:
193 return [], []
194
Terry Jan Reedy12131232019-08-04 19:48:52 -0400195 elif mode == FILES:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000196 if what == "":
197 what = "."
198 try:
199 expandedpath = os.path.expanduser(what)
200 bigl = os.listdir(expandedpath)
201 bigl.sort()
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000202 smalll = [s for s in bigl if s[:1] != '.']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000203 except OSError:
204 return [], []
205
206 if not smalll:
207 smalll = bigl
208 return smalll, bigl
209
210 def get_entity(self, name):
Terry Jan Reedy0fe45132019-03-24 17:12:28 -0400211 "Lookup name in a namespace spanning sys.modules and __main.dict__."
212 return eval(name, {**sys.modules, **__main__.__dict__})
Terry Jan Reedye3fcfc22014-06-03 20:54:21 -0400213
214
wohlganger58fc71c2017-09-10 16:19:47 -0500215AutoComplete.reload()
216
Terry Jan Reedye3fcfc22014-06-03 20:54:21 -0400217if __name__ == '__main__':
218 from unittest import main
219 main('idlelib.idle_test.test_autocomplete', verbosity=2)