1
"""AutoComplete.py - An IDLE extension for automatically completing names.
3
This extension can complete either attribute names of file names. It can pop
4
a window with all available names, for the user to select from.
10
from idlelib.configHandler import idleConf
12
# This string includes all chars that may be in an identifier
13
ID_CHARS = string.ascii_letters + string.digits + "_"
15
# These constants represent the two different types of completions
16
COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1)
18
from idlelib import AutoCompleteWindow
19
from idlelib.HyperParser import HyperParser
24
if os.altsep: # e.g. '/' on Windows...
31
("Show Completions", "<<force-open-completions>>"),
35
popupwait = idleConf.GetOption("extensions", "AutoComplete",
36
"popupwait", type="int", default=0)
38
def __init__(self, editwin=None):
39
self.editwin = editwin
40
if editwin is None: # subprocess and test
42
self.text = editwin.text
43
self.autocompletewindow = None
45
# id of delayed call, and the index of the text insert when the delayed
46
# call was issued. If _delayed_completion_id is None, there is no
48
self._delayed_completion_id = None
49
self._delayed_completion_index = None
51
def _make_autocomplete_window(self):
52
return AutoCompleteWindow.AutoCompleteWindow(self.text)
54
def _remove_autocomplete_window(self, event=None):
55
if self.autocompletewindow:
56
self.autocompletewindow.hide_window()
57
self.autocompletewindow = None
59
def force_open_completions_event(self, event):
60
"""Happens when the user really wants to open a completion list, even
61
if a function call is needed.
63
self.open_completions(True, False, True)
65
def try_open_completions_event(self, event):
66
"""Happens when it would be nice to open a completion list, but not
67
really necessary, for example after an dot, so function
70
lastchar = self.text.get("insert-1c")
72
self._open_completions_later(False, False, False,
74
elif lastchar in SEPS:
75
self._open_completions_later(False, False, False,
78
def autocomplete_event(self, event):
79
"""Happens when the user wants to complete his word, and if necessary,
80
open a completion list after that (if there is more than one
83
if hasattr(event, "mc_state") and event.mc_state:
84
# A modifier was pressed along with the tab, continue as usual.
86
if self.autocompletewindow and self.autocompletewindow.is_active():
87
self.autocompletewindow.complete()
90
opened = self.open_completions(False, True, True)
94
def _open_completions_later(self, *args):
95
self._delayed_completion_index = self.text.index("insert")
96
if self._delayed_completion_id is not None:
97
self.text.after_cancel(self._delayed_completion_id)
98
self._delayed_completion_id = \
99
self.text.after(self.popupwait, self._delayed_open_completions,
102
def _delayed_open_completions(self, *args):
103
self._delayed_completion_id = None
104
if self.text.index("insert") != self._delayed_completion_index:
106
self.open_completions(*args)
108
def open_completions(self, evalfuncs, complete, userWantsWin, mode=None):
109
"""Find the completions and create the AutoCompleteWindow.
110
Return True if successful (no syntax error or so found).
111
if complete is True, then if there's nothing to complete and no
112
start of completion, won't open completions and return False.
113
If mode is given, will open a completion list only in this mode.
115
# Cancel another delayed call, if it exists.
116
if self._delayed_completion_id is not None:
117
self.text.after_cancel(self._delayed_completion_id)
118
self._delayed_completion_id = None
120
hp = HyperParser(self.editwin, "insert")
121
curline = self.text.get("insert linestart", "insert")
123
if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
124
# Find the beginning of the string
125
# fetch_completions will look at the file system to determine whether the
126
# string value constitutes an actual file name
127
# XXX could consider raw strings here and unescape the string value if it's
129
self._remove_autocomplete_window()
130
mode = COMPLETE_FILES
131
# Find last separator or string start
132
while i and curline[i-1] not in "'\"" + SEPS:
134
comp_start = curline[i:j]
137
while i and curline[i-1] not in "'\"":
139
comp_what = curline[i:j]
140
elif hp.is_in_code() and (not mode or mode==COMPLETE_ATTRIBUTES):
141
self._remove_autocomplete_window()
142
mode = COMPLETE_ATTRIBUTES
143
while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127):
145
comp_start = curline[i:j]
146
if i and curline[i-1] == '.':
147
hp.set_index("insert-%dc" % (len(curline)-(i-1)))
148
comp_what = hp.get_expression()
149
if not comp_what or \
150
(not evalfuncs and comp_what.find('(') != -1):
157
if complete and not comp_what and not comp_start:
159
comp_lists = self.fetch_completions(comp_what, mode)
160
if not comp_lists[0]:
162
self.autocompletewindow = self._make_autocomplete_window()
163
return not self.autocompletewindow.show_window(
164
comp_lists, "insert-%dc" % len(comp_start),
165
complete, mode, userWantsWin)
167
def fetch_completions(self, what, mode):
168
"""Return a pair of lists of completions for something. The first list
169
is a sublist of the second. Both are sorted.
171
If there is a Python subprocess, get the comp. list there. Otherwise,
172
either fetch_completions() is running in the subprocess itself or it
173
was called in an IDLE EditorWindow before any script had been run.
175
The subprocess environment is that of the most recently run script. If
176
two unrelated modules are being edited some calltips in the current
177
module may be inoperative if the module was not the last to run.
180
rpcclt = self.editwin.flist.pyshell.interp.rpcclt
184
return rpcclt.remotecall("exec", "get_the_completion_list",
187
if mode == COMPLETE_ATTRIBUTES:
189
namespace = __main__.__dict__.copy()
190
namespace.update(__main__.__builtins__.__dict__)
191
bigl = eval("dir()", namespace)
193
if "__all__" in bigl:
194
smalll = sorted(eval("__all__", namespace))
196
smalll = [s for s in bigl if s[:1] != '_']
199
entity = self.get_entity(what)
202
if "__all__" in bigl:
203
smalll = sorted(entity.__all__)
205
smalll = [s for s in bigl if s[:1] != '_']
209
elif mode == COMPLETE_FILES:
213
expandedpath = os.path.expanduser(what)
214
bigl = os.listdir(expandedpath)
216
smalll = [s for s in bigl if s[:1] != '.']
224
def get_entity(self, name):
225
"""Lookup name in a namespace spanning sys.modules and __main.dict__"""
226
namespace = sys.modules.copy()
227
namespace.update(__main__.__dict__)
228
return eval(name, namespace)