##// END OF EJS Templates
Fix completion when importing modules in the cwd.
Thomas Kluyver -
Show More
@@ -1,321 +1,324 b''
1 """Implementations for various useful completers.
1 """Implementations for various useful completers.
2
2
3 These are all loaded by default by IPython.
3 These are all loaded by default by IPython.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010-2011 The IPython Development Team.
6 # Copyright (C) 2010-2011 The IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the BSD License.
8 # Distributed under the terms of the BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib imports
18 # Stdlib imports
19 import glob
19 import glob
20 import inspect
20 import inspect
21 import os
21 import os
22 import re
22 import re
23 import sys
23 import sys
24
24
25 # Third-party imports
25 # Third-party imports
26 from time import time
26 from time import time
27 from zipimport import zipimporter
27 from zipimport import zipimporter
28
28
29 # Our own imports
29 # Our own imports
30 from IPython.core.completer import expand_user, compress_user
30 from IPython.core.completer import expand_user, compress_user
31 from IPython.core.error import TryNext
31 from IPython.core.error import TryNext
32 from IPython.utils import py3compat
32 from IPython.utils import py3compat
33 from IPython.utils._process_common import arg_split
33 from IPython.utils._process_common import arg_split
34
34
35 # FIXME: this should be pulled in with the right call via the component system
35 # FIXME: this should be pulled in with the right call via the component system
36 from IPython.core.ipapi import get as get_ipython
36 from IPython.core.ipapi import get as get_ipython
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Globals and constants
39 # Globals and constants
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 # Time in seconds after which the rootmodules will be stored permanently in the
42 # Time in seconds after which the rootmodules will be stored permanently in the
43 # ipython ip.db database (kept in the user's .ipython dir).
43 # ipython ip.db database (kept in the user's .ipython dir).
44 TIMEOUT_STORAGE = 2
44 TIMEOUT_STORAGE = 2
45
45
46 # Time in seconds after which we give up
46 # Time in seconds after which we give up
47 TIMEOUT_GIVEUP = 20
47 TIMEOUT_GIVEUP = 20
48
48
49 # Regular expression for the python import statement
49 # Regular expression for the python import statement
50 import_re = re.compile(r'.*(\.so|\.py[cod]?)$')
50 import_re = re.compile(r'.*(\.so|\.py[cod]?)$')
51
51
52 # RE for the ipython %run command (python + ipython scripts)
52 # RE for the ipython %run command (python + ipython scripts)
53 magic_run_re = re.compile(r'.*(\.ipy|\.py[w]?)$')
53 magic_run_re = re.compile(r'.*(\.ipy|\.py[w]?)$')
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Local utilities
56 # Local utilities
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 def module_list(path):
59 def module_list(path):
60 """
60 """
61 Return the list containing the names of the modules available in the given
61 Return the list containing the names of the modules available in the given
62 folder.
62 folder.
63 """
63 """
64 # sys.path has the cwd as an empty string, but isdir/listdir need it as '.'
65 if path == '':
66 path = '.'
64
67
65 if os.path.isdir(path):
68 if os.path.isdir(path):
66 folder_list = os.listdir(path)
69 folder_list = os.listdir(path)
67 elif path.endswith('.egg'):
70 elif path.endswith('.egg'):
68 try:
71 try:
69 folder_list = [f for f in zipimporter(path)._files]
72 folder_list = [f for f in zipimporter(path)._files]
70 except:
73 except:
71 folder_list = []
74 folder_list = []
72 else:
75 else:
73 folder_list = []
76 folder_list = []
74
77
75 if not folder_list:
78 if not folder_list:
76 return []
79 return []
77
80
78 # A few local constants to be used in loops below
81 # A few local constants to be used in loops below
79 isfile = os.path.isfile
82 isfile = os.path.isfile
80 pjoin = os.path.join
83 pjoin = os.path.join
81 basename = os.path.basename
84 basename = os.path.basename
82
85
83 # Now find actual path matches for packages or modules
86 # Now find actual path matches for packages or modules
84 folder_list = [p for p in folder_list
87 folder_list = [p for p in folder_list
85 if isfile(pjoin(path, p,'__init__.py'))
88 if isfile(pjoin(path, p,'__init__.py'))
86 or import_re.match(p) ]
89 or import_re.match(p) ]
87
90
88 return [basename(p).split('.')[0] for p in folder_list]
91 return [basename(p).split('.')[0] for p in folder_list]
89
92
90 def get_root_modules():
93 def get_root_modules():
91 """
94 """
92 Returns a list containing the names of all the modules available in the
95 Returns a list containing the names of all the modules available in the
93 folders of the pythonpath.
96 folders of the pythonpath.
94 """
97 """
95 ip = get_ipython()
98 ip = get_ipython()
96
99
97 if 'rootmodules' in ip.db:
100 if 'rootmodules' in ip.db:
98 return ip.db['rootmodules']
101 return ip.db['rootmodules']
99
102
100 t = time()
103 t = time()
101 store = False
104 store = False
102 modules = list(sys.builtin_module_names)
105 modules = list(sys.builtin_module_names)
103 for path in sys.path:
106 for path in sys.path:
104 modules += module_list(path)
107 modules += module_list(path)
105 if time() - t >= TIMEOUT_STORAGE and not store:
108 if time() - t >= TIMEOUT_STORAGE and not store:
106 store = True
109 store = True
107 print("\nCaching the list of root modules, please wait!")
110 print("\nCaching the list of root modules, please wait!")
108 print("(This will only be done once - type '%rehashx' to "
111 print("(This will only be done once - type '%rehashx' to "
109 "reset cache!)\n")
112 "reset cache!)\n")
110 sys.stdout.flush()
113 sys.stdout.flush()
111 if time() - t > TIMEOUT_GIVEUP:
114 if time() - t > TIMEOUT_GIVEUP:
112 print("This is taking too long, we give up.\n")
115 print("This is taking too long, we give up.\n")
113 ip.db['rootmodules'] = []
116 ip.db['rootmodules'] = []
114 return []
117 return []
115
118
116 modules = set(modules)
119 modules = set(modules)
117 if '__init__' in modules:
120 if '__init__' in modules:
118 modules.remove('__init__')
121 modules.remove('__init__')
119 modules = list(modules)
122 modules = list(modules)
120 if store:
123 if store:
121 ip.db['rootmodules'] = modules
124 ip.db['rootmodules'] = modules
122 return modules
125 return modules
123
126
124
127
125 def is_importable(module, attr, only_modules):
128 def is_importable(module, attr, only_modules):
126 if only_modules:
129 if only_modules:
127 return inspect.ismodule(getattr(module, attr))
130 return inspect.ismodule(getattr(module, attr))
128 else:
131 else:
129 return not(attr[:2] == '__' and attr[-2:] == '__')
132 return not(attr[:2] == '__' and attr[-2:] == '__')
130
133
131
134
132 def try_import(mod, only_modules=False):
135 def try_import(mod, only_modules=False):
133 try:
136 try:
134 m = __import__(mod)
137 m = __import__(mod)
135 except:
138 except:
136 return []
139 return []
137 mods = mod.split('.')
140 mods = mod.split('.')
138 for module in mods[1:]:
141 for module in mods[1:]:
139 m = getattr(m, module)
142 m = getattr(m, module)
140
143
141 m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__
144 m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__
142
145
143 completions = []
146 completions = []
144 if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
147 if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
145 completions.extend( [attr for attr in dir(m) if
148 completions.extend( [attr for attr in dir(m) if
146 is_importable(m, attr, only_modules)])
149 is_importable(m, attr, only_modules)])
147
150
148 completions.extend(getattr(m, '__all__', []))
151 completions.extend(getattr(m, '__all__', []))
149 if m_is_init:
152 if m_is_init:
150 completions.extend(module_list(os.path.dirname(m.__file__)))
153 completions.extend(module_list(os.path.dirname(m.__file__)))
151 completions = set(completions)
154 completions = set(completions)
152 if '__init__' in completions:
155 if '__init__' in completions:
153 completions.remove('__init__')
156 completions.remove('__init__')
154 return list(completions)
157 return list(completions)
155
158
156
159
157 #-----------------------------------------------------------------------------
160 #-----------------------------------------------------------------------------
158 # Completion-related functions.
161 # Completion-related functions.
159 #-----------------------------------------------------------------------------
162 #-----------------------------------------------------------------------------
160
163
161 def quick_completer(cmd, completions):
164 def quick_completer(cmd, completions):
162 """ Easily create a trivial completer for a command.
165 """ Easily create a trivial completer for a command.
163
166
164 Takes either a list of completions, or all completions in string (that will
167 Takes either a list of completions, or all completions in string (that will
165 be split on whitespace).
168 be split on whitespace).
166
169
167 Example::
170 Example::
168
171
169 [d:\ipython]|1> import ipy_completers
172 [d:\ipython]|1> import ipy_completers
170 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
173 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
171 [d:\ipython]|3> foo b<TAB>
174 [d:\ipython]|3> foo b<TAB>
172 bar baz
175 bar baz
173 [d:\ipython]|3> foo ba
176 [d:\ipython]|3> foo ba
174 """
177 """
175
178
176 if isinstance(completions, basestring):
179 if isinstance(completions, basestring):
177 completions = completions.split()
180 completions = completions.split()
178
181
179 def do_complete(self, event):
182 def do_complete(self, event):
180 return completions
183 return completions
181
184
182 get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
185 get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
183
186
184 def module_completion(line):
187 def module_completion(line):
185 """
188 """
186 Returns a list containing the completion possibilities for an import line.
189 Returns a list containing the completion possibilities for an import line.
187
190
188 The line looks like this :
191 The line looks like this :
189 'import xml.d'
192 'import xml.d'
190 'from xml.dom import'
193 'from xml.dom import'
191 """
194 """
192
195
193 words = line.split(' ')
196 words = line.split(' ')
194 nwords = len(words)
197 nwords = len(words)
195
198
196 # from whatever <tab> -> 'import '
199 # from whatever <tab> -> 'import '
197 if nwords == 3 and words[0] == 'from':
200 if nwords == 3 and words[0] == 'from':
198 return ['import ']
201 return ['import ']
199
202
200 # 'from xy<tab>' or 'import xy<tab>'
203 # 'from xy<tab>' or 'import xy<tab>'
201 if nwords < 3 and (words[0] in ['import','from']) :
204 if nwords < 3 and (words[0] in ['import','from']) :
202 if nwords == 1:
205 if nwords == 1:
203 return get_root_modules()
206 return get_root_modules()
204 mod = words[1].split('.')
207 mod = words[1].split('.')
205 if len(mod) < 2:
208 if len(mod) < 2:
206 return get_root_modules()
209 return get_root_modules()
207 completion_list = try_import('.'.join(mod[:-1]), True)
210 completion_list = try_import('.'.join(mod[:-1]), True)
208 return ['.'.join(mod[:-1] + [el]) for el in completion_list]
211 return ['.'.join(mod[:-1] + [el]) for el in completion_list]
209
212
210 # 'from xyz import abc<tab>'
213 # 'from xyz import abc<tab>'
211 if nwords >= 3 and words[0] == 'from':
214 if nwords >= 3 and words[0] == 'from':
212 mod = words[1]
215 mod = words[1]
213 return try_import(mod)
216 return try_import(mod)
214
217
215 #-----------------------------------------------------------------------------
218 #-----------------------------------------------------------------------------
216 # Completers
219 # Completers
217 #-----------------------------------------------------------------------------
220 #-----------------------------------------------------------------------------
218 # These all have the func(self, event) signature to be used as custom
221 # These all have the func(self, event) signature to be used as custom
219 # completers
222 # completers
220
223
221 def module_completer(self,event):
224 def module_completer(self,event):
222 """Give completions after user has typed 'import ...' or 'from ...'"""
225 """Give completions after user has typed 'import ...' or 'from ...'"""
223
226
224 # This works in all versions of python. While 2.5 has
227 # This works in all versions of python. While 2.5 has
225 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
228 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
226 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
229 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
227 # of possibly problematic side effects.
230 # of possibly problematic side effects.
228 # This search the folders in the sys.path for available modules.
231 # This search the folders in the sys.path for available modules.
229
232
230 return module_completion(event.line)
233 return module_completion(event.line)
231
234
232 # FIXME: there's a lot of logic common to the run, cd and builtin file
235 # FIXME: there's a lot of logic common to the run, cd and builtin file
233 # completers, that is currently reimplemented in each.
236 # completers, that is currently reimplemented in each.
234
237
235 def magic_run_completer(self, event):
238 def magic_run_completer(self, event):
236 """Complete files that end in .py or .ipy for the %run command.
239 """Complete files that end in .py or .ipy for the %run command.
237 """
240 """
238 comps = arg_split(event.line, strict=False)
241 comps = arg_split(event.line, strict=False)
239 relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
242 relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
240
243
241 #print("\nev=", event) # dbg
244 #print("\nev=", event) # dbg
242 #print("rp=", relpath) # dbg
245 #print("rp=", relpath) # dbg
243 #print('comps=', comps) # dbg
246 #print('comps=', comps) # dbg
244
247
245 lglob = glob.glob
248 lglob = glob.glob
246 isdir = os.path.isdir
249 isdir = os.path.isdir
247 relpath, tilde_expand, tilde_val = expand_user(relpath)
250 relpath, tilde_expand, tilde_val = expand_user(relpath)
248
251
249 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
252 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
250
253
251 # Find if the user has already typed the first filename, after which we
254 # Find if the user has already typed the first filename, after which we
252 # should complete on all files, since after the first one other files may
255 # should complete on all files, since after the first one other files may
253 # be arguments to the input script.
256 # be arguments to the input script.
254
257
255 if filter(magic_run_re.match, comps):
258 if filter(magic_run_re.match, comps):
256 pys = [f.replace('\\','/') for f in lglob('*')]
259 pys = [f.replace('\\','/') for f in lglob('*')]
257 else:
260 else:
258 pys = [f.replace('\\','/')
261 pys = [f.replace('\\','/')
259 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
262 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
260 lglob(relpath + '*.pyw')]
263 lglob(relpath + '*.pyw')]
261 #print('run comp:', dirs+pys) # dbg
264 #print('run comp:', dirs+pys) # dbg
262 return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys]
265 return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys]
263
266
264
267
265 def cd_completer(self, event):
268 def cd_completer(self, event):
266 """Completer function for cd, which only returns directories."""
269 """Completer function for cd, which only returns directories."""
267 ip = get_ipython()
270 ip = get_ipython()
268 relpath = event.symbol
271 relpath = event.symbol
269
272
270 #print(event) # dbg
273 #print(event) # dbg
271 if event.line.endswith('-b') or ' -b ' in event.line:
274 if event.line.endswith('-b') or ' -b ' in event.line:
272 # return only bookmark completions
275 # return only bookmark completions
273 bkms = self.db.get('bookmarks', None)
276 bkms = self.db.get('bookmarks', None)
274 if bkms:
277 if bkms:
275 return bkms.keys()
278 return bkms.keys()
276 else:
279 else:
277 return []
280 return []
278
281
279 if event.symbol == '-':
282 if event.symbol == '-':
280 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
283 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
281 # jump in directory history by number
284 # jump in directory history by number
282 fmt = '-%0' + width_dh +'d [%s]'
285 fmt = '-%0' + width_dh +'d [%s]'
283 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
286 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
284 if len(ents) > 1:
287 if len(ents) > 1:
285 return ents
288 return ents
286 return []
289 return []
287
290
288 if event.symbol.startswith('--'):
291 if event.symbol.startswith('--'):
289 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
292 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
290
293
291 # Expand ~ in path and normalize directory separators.
294 # Expand ~ in path and normalize directory separators.
292 relpath, tilde_expand, tilde_val = expand_user(relpath)
295 relpath, tilde_expand, tilde_val = expand_user(relpath)
293 relpath = relpath.replace('\\','/')
296 relpath = relpath.replace('\\','/')
294
297
295 found = []
298 found = []
296 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
299 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
297 if os.path.isdir(f)]:
300 if os.path.isdir(f)]:
298 if ' ' in d:
301 if ' ' in d:
299 # we don't want to deal with any of that, complex code
302 # we don't want to deal with any of that, complex code
300 # for this is elsewhere
303 # for this is elsewhere
301 raise TryNext
304 raise TryNext
302
305
303 found.append(d)
306 found.append(d)
304
307
305 if not found:
308 if not found:
306 if os.path.isdir(relpath):
309 if os.path.isdir(relpath):
307 return [compress_user(relpath, tilde_expand, tilde_val)]
310 return [compress_user(relpath, tilde_expand, tilde_val)]
308
311
309 # if no completions so far, try bookmarks
312 # if no completions so far, try bookmarks
310 bks = self.db.get('bookmarks',{}).iterkeys()
313 bks = self.db.get('bookmarks',{}).iterkeys()
311 bkmatches = [s for s in bks if s.startswith(event.symbol)]
314 bkmatches = [s for s in bks if s.startswith(event.symbol)]
312 if bkmatches:
315 if bkmatches:
313 return bkmatches
316 return bkmatches
314
317
315 raise TryNext
318 raise TryNext
316
319
317 return [compress_user(p, tilde_expand, tilde_val) for p in found]
320 return [compress_user(p, tilde_expand, tilde_val) for p in found]
318
321
319 def reset_completer(self, event):
322 def reset_completer(self, event):
320 "A completer for %reset magic"
323 "A completer for %reset magic"
321 return '-f -s in out array dhist'.split()
324 return '-f -s in out array dhist'.split()
General Comments 0
You need to be logged in to leave comments. Login now