blob: bed2b8c4cd09c7b54162f62793b58d7a12155c4f [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
10from configHandler import idleConf
11
12import AutoCompleteWindow
13from HyperParser import HyperParser
14
15import __main__
16
17# This string includes all chars that may be in a file name (without a path
18# separator)
19FILENAME_CHARS = string.ascii_letters + string.digits + os.curdir + "._~#$:-"
20# This string includes all chars that may be in an identifier
21ID_CHARS = string.ascii_letters + string.digits + "_"
22
23# These constants represent the two different types of completions
24COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1)
25
26class AutoComplete:
27
28 menudefs = [
29 ('edit', [
Kurt B. Kaiser67bd62f2007-10-04 01:49:54 +000030 ("Show Completions", "<<force-open-completions>>"),
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000031 ])
32 ]
33
34 popupwait = idleConf.GetOption("extensions", "AutoComplete",
35 "popupwait", type="int", default=0)
36
37 def __init__(self, editwin=None):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000038 self.editwin = editwin
Benjamin Peterson5b63acd2008-03-29 15:24:25 +000039 if editwin is None: # subprocess and test
40 return
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000041 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:
192 smalll = filter(lambda s: s[:1] != '_', bigl)
193 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:
202 smalll = filter(lambda s: s[:1] != '_', bigl)
203 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()
213 smalll = filter(lambda s: s[:1] != '.', bigl)
214 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)