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