blob: 1bc33012f862eb400d5199d4cf7272573cb127f0 [file] [log] [blame]
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001"""AutoComplete.py - An IDLE extension for automatically completing names.
2
3This extension can complete either attribute names of file names. It can pop
4a window with all available names, for the user to select from.
5"""
6import os
7import sys
8import string
9
Guido van Rossum36e0a922007-07-20 04:05:57 +000010from .configHandler import idleConf
11from . import AutoCompleteWindow
12from .HyperParser import HyperParser
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000013
14import __main__
15
16# This string includes all chars that may be in a file name (without a path
17# separator)
18FILENAME_CHARS = string.ascii_letters + string.digits + os.curdir + "._~#$:-"
19# This string includes all chars that may be in an identifier
20ID_CHARS = string.ascii_letters + string.digits + "_"
21
22# These constants represent the two different types of completions
23COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1)
24
25class AutoComplete:
26
27 menudefs = [
28 ('edit', [
29 ("Show completions", "<<force-open-completions>>"),
30 ])
31 ]
32
33 popupwait = idleConf.GetOption("extensions", "AutoComplete",
34 "popupwait", type="int", default=0)
35
36 def __init__(self, editwin=None):
37 if editwin == None: # subprocess and test
38 self.editwin = None
39 return
40 self.editwin = editwin
41 self.text = editwin.text
42 self.autocompletewindow = None
43
44 # id of delayed call, and the index of the text insert when the delayed
45 # call was issued. If _delayed_completion_id is None, there is no
46 # delayed call.
47 self._delayed_completion_id = None
48 self._delayed_completion_index = None
49
50 def _make_autocomplete_window(self):
51 return AutoCompleteWindow.AutoCompleteWindow(self.text)
52
53 def _remove_autocomplete_window(self, event=None):
54 if self.autocompletewindow:
55 self.autocompletewindow.hide_window()
56 self.autocompletewindow = None
57
58 def force_open_completions_event(self, event):
59 """Happens when the user really wants to open a completion list, even
60 if a function call is needed.
61 """
62 self.open_completions(True, False, True)
63
64 def try_open_completions_event(self, event):
65 """Happens when it would be nice to open a completion list, but not
66 really neccesary, for example after an dot, so function
67 calls won't be made.
68 """
69 lastchar = self.text.get("insert-1c")
70 if lastchar == ".":
71 self._open_completions_later(False, False, False,
72 COMPLETE_ATTRIBUTES)
73 elif lastchar == os.sep:
74 self._open_completions_later(False, False, False,
75 COMPLETE_FILES)
76
77 def autocomplete_event(self, event):
78 """Happens when the user wants to complete his word, and if neccesary,
79 open a completion list after that (if there is more than one
80 completion)
81 """
82 if hasattr(event, "mc_state") and event.mc_state:
83 # A modifier was pressed along with the tab, continue as usual.
84 return
85 if self.autocompletewindow and self.autocompletewindow.is_active():
86 self.autocompletewindow.complete()
87 return "break"
88 else:
89 opened = self.open_completions(False, True, True)
90 if opened:
91 return "break"
92
93 def _open_completions_later(self, *args):
94 self._delayed_completion_index = self.text.index("insert")
95 if self._delayed_completion_id is not None:
96 self.text.after_cancel(self._delayed_completion_id)
97 self._delayed_completion_id = \
98 self.text.after(self.popupwait, self._delayed_open_completions,
99 *args)
100
101 def _delayed_open_completions(self, *args):
102 self._delayed_completion_id = None
103 if self.text.index("insert") != self._delayed_completion_index:
104 return
105 self.open_completions(*args)
106
107 def open_completions(self, evalfuncs, complete, userWantsWin, mode=None):
108 """Find the completions and create the AutoCompleteWindow.
109 Return True if successful (no syntax error or so found).
110 if complete is True, then if there's nothing to complete and no
111 start of completion, won't open completions and return False.
112 If mode is given, will open a completion list only in this mode.
113 """
114 # Cancel another delayed call, if it exists.
115 if self._delayed_completion_id is not None:
116 self.text.after_cancel(self._delayed_completion_id)
117 self._delayed_completion_id = None
118
119 hp = HyperParser(self.editwin, "insert")
120 curline = self.text.get("insert linestart", "insert")
121 i = j = len(curline)
122 if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
123 self._remove_autocomplete_window()
124 mode = COMPLETE_FILES
125 while i and curline[i-1] in FILENAME_CHARS:
126 i -= 1
127 comp_start = curline[i:j]
128 j = i
129 while i and curline[i-1] in FILENAME_CHARS+os.sep:
130 i -= 1
131 comp_what = curline[i:j]
132 elif hp.is_in_code() and (not mode or mode==COMPLETE_ATTRIBUTES):
133 self._remove_autocomplete_window()
134 mode = COMPLETE_ATTRIBUTES
135 while i and curline[i-1] in ID_CHARS:
136 i -= 1
137 comp_start = curline[i:j]
138 if i and curline[i-1] == '.':
139 hp.set_index("insert-%dc" % (len(curline)-(i-1)))
140 comp_what = hp.get_expression()
141 if not comp_what or \
142 (not evalfuncs and comp_what.find('(') != -1):
143 return
144 else:
145 comp_what = ""
146 else:
147 return
148
149 if complete and not comp_what and not comp_start:
150 return
151 comp_lists = self.fetch_completions(comp_what, mode)
152 if not comp_lists[0]:
153 return
154 self.autocompletewindow = self._make_autocomplete_window()
155 self.autocompletewindow.show_window(comp_lists,
156 "insert-%dc" % len(comp_start),
157 complete,
158 mode,
159 userWantsWin)
160 return True
161
162 def fetch_completions(self, what, mode):
163 """Return a pair of lists of completions for something. The first list
164 is a sublist of the second. Both are sorted.
165
166 If there is a Python subprocess, get the comp. list there. Otherwise,
167 either fetch_completions() is running in the subprocess itself or it
168 was called in an IDLE EditorWindow before any script had been run.
169
170 The subprocess environment is that of the most recently run script. If
171 two unrelated modules are being edited some calltips in the current
172 module may be inoperative if the module was not the last to run.
173 """
174 try:
175 rpcclt = self.editwin.flist.pyshell.interp.rpcclt
176 except:
177 rpcclt = None
178 if rpcclt:
179 return rpcclt.remotecall("exec", "get_the_completion_list",
180 (what, mode), {})
181 else:
182 if mode == COMPLETE_ATTRIBUTES:
183 if what == "":
184 namespace = __main__.__dict__.copy()
185 namespace.update(__main__.__builtins__.__dict__)
186 bigl = eval("dir()", namespace)
187 bigl.sort()
188 if "__all__" in bigl:
189 smalll = eval("__all__", namespace)
190 smalll.sort()
191 else:
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000192 smalll = [s for s in bigl if s[:1] != '_']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000193 else:
194 try:
195 entity = self.get_entity(what)
196 bigl = dir(entity)
197 bigl.sort()
198 if "__all__" in bigl:
199 smalll = entity.__all__
200 smalll.sort()
201 else:
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:
204 return [], []
205
206 elif mode == COMPLETE_FILES:
207 if what == "":
208 what = "."
209 try:
210 expandedpath = os.path.expanduser(what)
211 bigl = os.listdir(expandedpath)
212 bigl.sort()
Kurt B. Kaiserf2335a92007-08-10 02:41:21 +0000213 smalll = [s for s in bigl if s[:1] != '.']
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000214 except OSError:
215 return [], []
216
217 if not smalll:
218 smalll = bigl
219 return smalll, bigl
220
221 def get_entity(self, name):
222 """Lookup name in a namespace spanning sys.modules and __main.dict__"""
223 namespace = sys.modules.copy()
224 namespace.update(__main__.__dict__)
225 return eval(name, namespace)