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