##// END OF EJS Templates
make %run magic complete on .ipynb files
Paul Ivanov -
Show More
@@ -1,338 +1,338 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|\.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 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 = (len(comps) > 1 and comps[-1] or '').strip("'\"")
257
257
258 #print("\nev=", event) # dbg
258 #print("\nev=", event) # dbg
259 #print("rp=", relpath) # dbg
259 #print("rp=", relpath) # dbg
260 #print('comps=', comps) # dbg
260 #print('comps=', comps) # dbg
261
261
262 lglob = glob.glob
262 lglob = glob.glob
263 isdir = os.path.isdir
263 isdir = os.path.isdir
264 relpath, tilde_expand, tilde_val = expand_user(relpath)
264 relpath, tilde_expand, tilde_val = expand_user(relpath)
265
265
266 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
266 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
267
267
268 # Find if the user has already typed the first filename, after which we
268 # 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
269 # should complete on all files, since after the first one other files may
270 # be arguments to the input script.
270 # be arguments to the input script.
271
271
272 if any(magic_run_re.match(c) for c in comps):
272 if any(magic_run_re.match(c) for c in comps):
273 pys = [f.replace('\\','/') for f in lglob('*')]
273 pys = [f.replace('\\','/') for f in lglob('*')]
274 else:
274 else:
275 pys = [f.replace('\\','/')
275 pys = [f.replace('\\','/')
276 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
276 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
277 lglob(relpath + '*.pyw')]
277 lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')]
278 #print('run comp:', dirs+pys) # dbg
278 #print('run comp:', dirs+pys) # dbg
279 return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys]
279 return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys]
280
280
281
281
282 def cd_completer(self, event):
282 def cd_completer(self, event):
283 """Completer function for cd, which only returns directories."""
283 """Completer function for cd, which only returns directories."""
284 ip = get_ipython()
284 ip = get_ipython()
285 relpath = event.symbol
285 relpath = event.symbol
286
286
287 #print(event) # dbg
287 #print(event) # dbg
288 if event.line.endswith('-b') or ' -b ' in event.line:
288 if event.line.endswith('-b') or ' -b ' in event.line:
289 # return only bookmark completions
289 # return only bookmark completions
290 bkms = self.db.get('bookmarks', None)
290 bkms = self.db.get('bookmarks', None)
291 if bkms:
291 if bkms:
292 return bkms.keys()
292 return bkms.keys()
293 else:
293 else:
294 return []
294 return []
295
295
296 if event.symbol == '-':
296 if event.symbol == '-':
297 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
297 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
298 # jump in directory history by number
298 # jump in directory history by number
299 fmt = '-%0' + width_dh +'d [%s]'
299 fmt = '-%0' + width_dh +'d [%s]'
300 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
300 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
301 if len(ents) > 1:
301 if len(ents) > 1:
302 return ents
302 return ents
303 return []
303 return []
304
304
305 if event.symbol.startswith('--'):
305 if event.symbol.startswith('--'):
306 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
306 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
307
307
308 # Expand ~ in path and normalize directory separators.
308 # Expand ~ in path and normalize directory separators.
309 relpath, tilde_expand, tilde_val = expand_user(relpath)
309 relpath, tilde_expand, tilde_val = expand_user(relpath)
310 relpath = relpath.replace('\\','/')
310 relpath = relpath.replace('\\','/')
311
311
312 found = []
312 found = []
313 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
313 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
314 if os.path.isdir(f)]:
314 if os.path.isdir(f)]:
315 if ' ' in d:
315 if ' ' in d:
316 # we don't want to deal with any of that, complex code
316 # we don't want to deal with any of that, complex code
317 # for this is elsewhere
317 # for this is elsewhere
318 raise TryNext
318 raise TryNext
319
319
320 found.append(d)
320 found.append(d)
321
321
322 if not found:
322 if not found:
323 if os.path.isdir(relpath):
323 if os.path.isdir(relpath):
324 return [compress_user(relpath, tilde_expand, tilde_val)]
324 return [compress_user(relpath, tilde_expand, tilde_val)]
325
325
326 # if no completions so far, try bookmarks
326 # if no completions so far, try bookmarks
327 bks = self.db.get('bookmarks',{})
327 bks = self.db.get('bookmarks',{})
328 bkmatches = [s for s in bks if s.startswith(event.symbol)]
328 bkmatches = [s for s in bks if s.startswith(event.symbol)]
329 if bkmatches:
329 if bkmatches:
330 return bkmatches
330 return bkmatches
331
331
332 raise TryNext
332 raise TryNext
333
333
334 return [compress_user(p, tilde_expand, tilde_val) for p in found]
334 return [compress_user(p, tilde_expand, tilde_val) for p in found]
335
335
336 def reset_completer(self, event):
336 def reset_completer(self, event):
337 "A completer for %reset magic"
337 "A completer for %reset magic"
338 return '-f -s in out array dhist'.split()
338 return '-f -s in out array dhist'.split()
General Comments 0
You need to be logged in to leave comments. Login now