- Notifications
You must be signed in to change notification settings - Fork 31.7k
/
Copy pathautocomplete.py
228 lines (201 loc) · 9.13 KB
/
autocomplete.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
"""Complete either attribute names or file names.
Either on demand or after a user-selected delay after a key character,
pop up a list of candidates.
"""
import__main__
importkeyword
importos
importstring
importsys
# Modified keyword list is used in fetch_completions.
completion_kwds= [sforsinkeyword.kwlist
ifsnotin {'True', 'False', 'None'}] # In builtins.
completion_kwds.extend(('match', 'case')) # Context keywords.
completion_kwds.sort()
# Two types of completions; defined here for autocomplete_w import below.
ATTRS, FILES=0, 1
fromidlelibimportautocomplete_w
fromidlelib.configimportidleConf
fromidlelib.hyperparserimportHyperParser
# Tuples passed to open_completions.
# EvalFunc, Complete, WantWin, Mode
FORCE=True, False, True, None# Control-Space.
TAB=False, True, True, None# Tab.
TRY_A=False, False, False, ATTRS# '.' for attributes.
TRY_F=False, False, False, FILES# '/' in quotes for file name.
# This string includes all chars that may be in an identifier.
# TODO Update this here and elsewhere.
ID_CHARS=string.ascii_letters+string.digits+"_"
SEPS=f"{os.sep}{os.altsepifos.altsepelse''}"
TRIGGERS=f".{SEPS}"
classAutoComplete:
def__init__(self, editwin=None, tags=None):
self.editwin=editwin
ifeditwinisnotNone: # not in subprocess or no-gui test
self.text=editwin.text
self.tags=tags
self.autocompletewindow=None
# id of delayed call, and the index of the text insert when
# the delayed call was issued. If _delayed_completion_id is
# None, there is no delayed call.
self._delayed_completion_id=None
self._delayed_completion_index=None
@classmethod
defreload(cls):
cls.popupwait=idleConf.GetOption(
"extensions", "AutoComplete", "popupwait", type="int", default=0)
def_make_autocomplete_window(self): # Makes mocking easier.
returnautocomplete_w.AutoCompleteWindow(self.text, tags=self.tags)
def_remove_autocomplete_window(self, event=None):
ifself.autocompletewindow:
self.autocompletewindow.hide_window()
self.autocompletewindow=None
defforce_open_completions_event(self, event):
"(^space) Open completion list, even if a function call is needed."
self.open_completions(FORCE)
return"break"
defautocomplete_event(self, event):
"(tab) Complete word or open list if multiple options."
ifhasattr(event, "mc_state") andevent.mc_stateor\
notself.text.get("insert linestart", "insert").strip():
# A modifier was pressed along with the tab or
# there is only previous whitespace on this line, so tab.
returnNone
ifself.autocompletewindowandself.autocompletewindow.is_active():
self.autocompletewindow.complete()
return"break"
else:
opened=self.open_completions(TAB)
return"break"ifopenedelseNone
deftry_open_completions_event(self, event=None):
"(./) Open completion list after pause with no movement."
lastchar=self.text.get("insert-1c")
iflastcharinTRIGGERS:
args=TRY_Aiflastchar=="."elseTRY_F
self._delayed_completion_index=self.text.index("insert")
ifself._delayed_completion_idisnotNone:
self.text.after_cancel(self._delayed_completion_id)
self._delayed_completion_id=self.text.after(
self.popupwait, self._delayed_open_completions, args)
def_delayed_open_completions(self, args):
"Call open_completions if index unchanged."
self._delayed_completion_id=None
ifself.text.index("insert") ==self._delayed_completion_index:
self.open_completions(args)
defopen_completions(self, args):
"""Find the completions and create the AutoCompleteWindow.
Return True if successful (no syntax error or so found).
If complete is True, then if there's nothing to complete and no
start of completion, won't open completions and return False.
If mode is given, will open a completion list only in this mode.
"""
evalfuncs, complete, wantwin, mode=args
# Cancel another delayed call, if it exists.
ifself._delayed_completion_idisnotNone:
self.text.after_cancel(self._delayed_completion_id)
self._delayed_completion_id=None
hp=HyperParser(self.editwin, "insert")
curline=self.text.get("insert linestart", "insert")
i=j=len(curline)
ifhp.is_in_string() and (notmodeormode==FILES):
# Find the beginning of the string.
# fetch_completions will look at the file system to determine
# whether the string value constitutes an actual file name
# XXX could consider raw strings here and unescape the string
# value if it's not raw.
self._remove_autocomplete_window()
mode=FILES
# Find last separator or string start
whileiandcurline[i-1] notin"'\""+SEPS:
i-=1
comp_start=curline[i:j]
j=i
# Find string start
whileiandcurline[i-1] notin"'\"":
i-=1
comp_what=curline[i:j]
elifhp.is_in_code() and (notmodeormode==ATTRS):
self._remove_autocomplete_window()
mode=ATTRS
whileiand (curline[i-1] inID_CHARSorord(curline[i-1]) >127):
i-=1
comp_start=curline[i:j]
ifiandcurline[i-1] =='.': # Need object with attributes.
hp.set_index("insert-%dc"% (len(curline)-(i-1)))
comp_what=hp.get_expression()
if (notcomp_whator
(notevalfuncsandcomp_what.find('(') !=-1)):
returnNone
else:
comp_what=""
else:
returnNone
ifcompleteandnotcomp_whatandnotcomp_start:
returnNone
comp_lists=self.fetch_completions(comp_what, mode)
ifnotcomp_lists[0]:
returnNone
self.autocompletewindow=self._make_autocomplete_window()
returnnotself.autocompletewindow.show_window(
comp_lists, "insert-%dc"%len(comp_start),
complete, mode, wantwin)
deffetch_completions(self, what, mode):
"""Return a pair of lists of completions for something. The first list
is a sublist of the second. Both are sorted.
If there is a Python subprocess, get the comp. list there. Otherwise,
either fetch_completions() is running in the subprocess itself or it
was called in an IDLE EditorWindow before any script had been run.
The subprocess environment is that of the most recently run script. If
two unrelated modules are being edited some calltips in the current
module may be inoperative if the module was not the last to run.
"""
try:
rpcclt=self.editwin.flist.pyshell.interp.rpcclt
except:
rpcclt=None
ifrpcclt:
returnrpcclt.remotecall("exec", "get_the_completion_list",
(what, mode), {})
else:
ifmode==ATTRS:
ifwhat=="": # Main module names.
namespace= {**__main__.__builtins__.__dict__,
**__main__.__dict__}
bigl=eval("dir()", namespace)
bigl.extend(completion_kwds)
bigl.sort()
if"__all__"inbigl:
smalll=sorted(eval("__all__", namespace))
else:
smalll= [sforsinbiglifs[:1] !='_']
else:
try:
entity=self.get_entity(what)
bigl=dir(entity)
bigl.sort()
if"__all__"inbigl:
smalll=sorted(entity.__all__)
else:
smalll= [sforsinbiglifs[:1] !='_']
except:
return [], []
elifmode==FILES:
ifwhat=="":
what="."
try:
expandedpath=os.path.expanduser(what)
bigl=os.listdir(expandedpath)
bigl.sort()
smalll= [sforsinbiglifs[:1] !='.']
exceptOSError:
return [], []
ifnotsmalll:
smalll=bigl
returnsmalll, bigl
defget_entity(self, name):
"Lookup name in a namespace spanning sys.modules and __main.dict__."
returneval(name, {**sys.modules, **__main__.__dict__})
AutoComplete.reload()
if__name__=='__main__':
fromunittestimportmain
main('idlelib.idle_test.test_autocomplete', verbosity=2)