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