| 
									
										
										
										
											2005-11-18 22:05:48 +00:00
										 |  |  | """AutoComplete.py - An IDLE extension for automatically completing names.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This extension can complete either attribute names of file names. It can pop | 
					
						
							|  |  |  | a window with all available names, for the user to select from. | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | import string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from configHandler import idleConf | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import AutoCompleteWindow | 
					
						
							|  |  |  | from HyperParser import HyperParser | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import __main__ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # This string includes all chars that may be in a file name (without a path | 
					
						
							|  |  |  | # separator) | 
					
						
							|  |  |  | FILENAME_CHARS = string.ascii_letters + string.digits + os.curdir + "._~#$:-" | 
					
						
							|  |  |  | # This string includes all chars that may be in an identifier | 
					
						
							|  |  |  | ID_CHARS = string.ascii_letters + string.digits + "_" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # These constants represent the two different types of completions | 
					
						
							|  |  |  | COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-27 21:52:19 +00:00
										 |  |  | SEPS = os.sep | 
					
						
							|  |  |  | if os.altsep:  # e.g. '/' on Windows... | 
					
						
							|  |  |  |     SEPS += os.altsep | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-18 22:05:48 +00:00
										 |  |  | class AutoComplete: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     menudefs = [ | 
					
						
							|  |  |  |         ('edit', [ | 
					
						
							| 
									
										
										
										
											2007-10-04 01:49:54 +00:00
										 |  |  |             ("Show Completions", "<<force-open-completions>>"), | 
					
						
							| 
									
										
										
										
											2005-11-18 22:05:48 +00:00
										 |  |  |         ]) | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     popupwait = idleConf.GetOption("extensions", "AutoComplete", | 
					
						
							|  |  |  |                                    "popupwait", type="int", default=0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, editwin=None): | 
					
						
							|  |  |  |         self.editwin = editwin | 
					
						
							| 
									
										
										
										
											2008-03-29 15:24:25 +00:00
										 |  |  |         if editwin is None:  # subprocess and test | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2005-11-18 22:05:48 +00:00
										 |  |  |         self.text = editwin.text | 
					
						
							|  |  |  |         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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _make_autocomplete_window(self): | 
					
						
							|  |  |  |         return AutoCompleteWindow.AutoCompleteWindow(self.text) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _remove_autocomplete_window(self, event=None): | 
					
						
							|  |  |  |         if self.autocompletewindow: | 
					
						
							|  |  |  |             self.autocompletewindow.hide_window() | 
					
						
							|  |  |  |             self.autocompletewindow = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def force_open_completions_event(self, event): | 
					
						
							|  |  |  |         """Happens when the user really wants to open a completion list, even
 | 
					
						
							|  |  |  |         if a function call is needed. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         self.open_completions(True, False, True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def try_open_completions_event(self, event): | 
					
						
							|  |  |  |         """Happens when it would be nice to open a completion list, but not
 | 
					
						
							|  |  |  |         really neccesary, for example after an dot, so function | 
					
						
							|  |  |  |         calls won't be made. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         lastchar = self.text.get("insert-1c") | 
					
						
							|  |  |  |         if lastchar == ".": | 
					
						
							|  |  |  |             self._open_completions_later(False, False, False, | 
					
						
							|  |  |  |                                          COMPLETE_ATTRIBUTES) | 
					
						
							| 
									
										
										
										
											2008-04-27 21:52:19 +00:00
										 |  |  |         elif lastchar in SEPS: | 
					
						
							| 
									
										
										
										
											2005-11-18 22:05:48 +00:00
										 |  |  |             self._open_completions_later(False, False, False, | 
					
						
							|  |  |  |                                          COMPLETE_FILES) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def autocomplete_event(self, event): | 
					
						
							|  |  |  |         """Happens when the user wants to complete his word, and if neccesary,
 | 
					
						
							|  |  |  |         open a completion list after that (if there is more than one | 
					
						
							|  |  |  |         completion) | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         if hasattr(event, "mc_state") and event.mc_state: | 
					
						
							|  |  |  |             # A modifier was pressed along with the tab, continue as usual. | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         if self.autocompletewindow and self.autocompletewindow.is_active(): | 
					
						
							|  |  |  |             self.autocompletewindow.complete() | 
					
						
							|  |  |  |             return "break" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             opened = self.open_completions(False, True, True) | 
					
						
							|  |  |  |             if opened: | 
					
						
							|  |  |  |                 return "break" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _open_completions_later(self, *args): | 
					
						
							|  |  |  |         self._delayed_completion_index = self.text.index("insert") | 
					
						
							|  |  |  |         if self._delayed_completion_id is not None: | 
					
						
							|  |  |  |             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): | 
					
						
							|  |  |  |         self._delayed_completion_id = None | 
					
						
							|  |  |  |         if self.text.index("insert") != self._delayed_completion_index: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         self.open_completions(*args) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def open_completions(self, evalfuncs, complete, userWantsWin, mode=None): | 
					
						
							|  |  |  |         """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. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         # Cancel another delayed call, if it exists. | 
					
						
							|  |  |  |         if self._delayed_completion_id is not None: | 
					
						
							|  |  |  |             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) | 
					
						
							|  |  |  |         if hp.is_in_string() and (not mode or mode==COMPLETE_FILES): | 
					
						
							|  |  |  |             self._remove_autocomplete_window() | 
					
						
							|  |  |  |             mode = COMPLETE_FILES | 
					
						
							|  |  |  |             while i and curline[i-1] in FILENAME_CHARS: | 
					
						
							|  |  |  |                 i -= 1 | 
					
						
							|  |  |  |             comp_start = curline[i:j] | 
					
						
							|  |  |  |             j = i | 
					
						
							| 
									
										
										
										
											2008-04-27 21:52:19 +00:00
										 |  |  |             while i and curline[i-1] in FILENAME_CHARS + SEPS: | 
					
						
							| 
									
										
										
										
											2005-11-18 22:05:48 +00:00
										 |  |  |                 i -= 1 | 
					
						
							|  |  |  |             comp_what = curline[i:j] | 
					
						
							|  |  |  |         elif hp.is_in_code() and (not mode or mode==COMPLETE_ATTRIBUTES): | 
					
						
							|  |  |  |             self._remove_autocomplete_window() | 
					
						
							|  |  |  |             mode = COMPLETE_ATTRIBUTES | 
					
						
							|  |  |  |             while i and curline[i-1] in ID_CHARS: | 
					
						
							|  |  |  |                 i -= 1 | 
					
						
							|  |  |  |             comp_start = curline[i:j] | 
					
						
							|  |  |  |             if i and curline[i-1] == '.': | 
					
						
							|  |  |  |                 hp.set_index("insert-%dc" % (len(curline)-(i-1))) | 
					
						
							|  |  |  |                 comp_what = hp.get_expression() | 
					
						
							|  |  |  |                 if not comp_what or \ | 
					
						
							|  |  |  |                    (not evalfuncs and comp_what.find('(') != -1): | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 comp_what = "" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if complete and not comp_what and not comp_start: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         comp_lists = self.fetch_completions(comp_what, mode) | 
					
						
							|  |  |  |         if not comp_lists[0]: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         self.autocompletewindow = self._make_autocomplete_window() | 
					
						
							|  |  |  |         self.autocompletewindow.show_window(comp_lists, | 
					
						
							|  |  |  |                                             "insert-%dc" % len(comp_start), | 
					
						
							|  |  |  |                                             complete, | 
					
						
							|  |  |  |                                             mode, | 
					
						
							|  |  |  |                                             userWantsWin) | 
					
						
							|  |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def fetch_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 | 
					
						
							|  |  |  |         if rpcclt: | 
					
						
							|  |  |  |             return rpcclt.remotecall("exec", "get_the_completion_list", | 
					
						
							|  |  |  |                                      (what, mode), {}) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             if mode == COMPLETE_ATTRIBUTES: | 
					
						
							|  |  |  |                 if what == "": | 
					
						
							|  |  |  |                     namespace = __main__.__dict__.copy() | 
					
						
							|  |  |  |                     namespace.update(__main__.__builtins__.__dict__) | 
					
						
							|  |  |  |                     bigl = eval("dir()", namespace) | 
					
						
							|  |  |  |                     bigl.sort() | 
					
						
							|  |  |  |                     if "__all__" in bigl: | 
					
						
							|  |  |  |                         smalll = eval("__all__", namespace) | 
					
						
							|  |  |  |                         smalll.sort() | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         smalll = filter(lambda s: s[:1] != '_', bigl) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         entity = self.get_entity(what) | 
					
						
							|  |  |  |                         bigl = dir(entity) | 
					
						
							|  |  |  |                         bigl.sort() | 
					
						
							|  |  |  |                         if "__all__" in bigl: | 
					
						
							|  |  |  |                             smalll = entity.__all__ | 
					
						
							|  |  |  |                             smalll.sort() | 
					
						
							|  |  |  |                         else: | 
					
						
							|  |  |  |                             smalll = filter(lambda s: s[:1] != '_', bigl) | 
					
						
							|  |  |  |                     except: | 
					
						
							|  |  |  |                         return [], [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             elif mode == COMPLETE_FILES: | 
					
						
							|  |  |  |                 if what == "": | 
					
						
							|  |  |  |                     what = "." | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     expandedpath = os.path.expanduser(what) | 
					
						
							|  |  |  |                     bigl = os.listdir(expandedpath) | 
					
						
							|  |  |  |                     bigl.sort() | 
					
						
							|  |  |  |                     smalll = filter(lambda s: s[:1] != '.', bigl) | 
					
						
							|  |  |  |                 except OSError: | 
					
						
							|  |  |  |                     return [], [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if not smalll: | 
					
						
							|  |  |  |                 smalll = bigl | 
					
						
							|  |  |  |             return smalll, bigl | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_entity(self, name): | 
					
						
							|  |  |  |         """Lookup name in a namespace spanning sys.modules and __main.dict__""" | 
					
						
							|  |  |  |         namespace = sys.modules.copy() | 
					
						
							|  |  |  |         namespace.update(__main__.__dict__) | 
					
						
							|  |  |  |         return eval(name, namespace) |