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