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