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