Show More
@@ -0,0 +1,35 b'' | |||||
|
1 | """Tests for the IPython tab-completion machinery. | |||
|
2 | """ | |||
|
3 | #----------------------------------------------------------------------------- | |||
|
4 | # Module imports | |||
|
5 | #----------------------------------------------------------------------------- | |||
|
6 | ||||
|
7 | # stdlib | |||
|
8 | import sys | |||
|
9 | ||||
|
10 | # third party | |||
|
11 | import nose.tools as nt | |||
|
12 | ||||
|
13 | # our own packages | |||
|
14 | from IPython.core import completer | |||
|
15 | ||||
|
16 | #----------------------------------------------------------------------------- | |||
|
17 | # Test functions | |||
|
18 | #----------------------------------------------------------------------------- | |||
|
19 | def test_protect_filename(): | |||
|
20 | pairs = [ ('abc','abc'), | |||
|
21 | (' abc',r'\ abc'), | |||
|
22 | ('a bc',r'a\ bc'), | |||
|
23 | ('a bc',r'a\ \ bc'), | |||
|
24 | (' bc',r'\ \ bc'), | |||
|
25 | ] | |||
|
26 | # On posix, we also protect parens | |||
|
27 | if sys.platform != 'win32': | |||
|
28 | pairs.extend( [('a(bc',r'a\(bc'), | |||
|
29 | ('a)bc',r'a\)bc'), | |||
|
30 | ('a( )bc',r'a\(\ \)bc'), | |||
|
31 | ] ) | |||
|
32 | # run the actual tests | |||
|
33 | for s1, s2 in pairs: | |||
|
34 | s1p = completer.protect_filename(s1) | |||
|
35 | nt.assert_equals(s1p, s2) |
@@ -44,7 +44,6 b' its input.' | |||||
44 |
|
44 | |||
45 | - When the original stdin is not a tty device, GNU readline is never |
|
45 | - When the original stdin is not a tty device, GNU readline is never | |
46 | used, and this module (and the readline module) are silently inactive. |
|
46 | used, and this module (and the readline module) are silently inactive. | |
47 |
|
||||
48 | """ |
|
47 | """ | |
49 |
|
48 | |||
50 | #***************************************************************************** |
|
49 | #***************************************************************************** | |
@@ -54,14 +53,19 b' used, and this module (and the readline module) are silently inactive.' | |||||
54 | # proper procedure is to maintain its copyright as belonging to the Python |
|
53 | # proper procedure is to maintain its copyright as belonging to the Python | |
55 | # Software Foundation (in addition to my own, for all new code). |
|
54 | # Software Foundation (in addition to my own, for all new code). | |
56 | # |
|
55 | # | |
|
56 | # Copyright (C) 2008-2010 IPython Development Team | |||
|
57 | # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> | |||
57 | # Copyright (C) 2001 Python Software Foundation, www.python.org |
|
58 | # Copyright (C) 2001 Python Software Foundation, www.python.org | |
58 | # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu> |
|
|||
59 | # |
|
59 | # | |
60 | # Distributed under the terms of the BSD License. The full license is in |
|
60 | # Distributed under the terms of the BSD License. The full license is in | |
61 | # the file COPYING, distributed as part of this software. |
|
61 | # the file COPYING, distributed as part of this software. | |
62 | # |
|
62 | # | |
63 | #***************************************************************************** |
|
63 | #***************************************************************************** | |
64 |
|
64 | |||
|
65 | #----------------------------------------------------------------------------- | |||
|
66 | # Imports | |||
|
67 | #----------------------------------------------------------------------------- | |||
|
68 | ||||
65 | import __builtin__ |
|
69 | import __builtin__ | |
66 | import __main__ |
|
70 | import __main__ | |
67 | import glob |
|
71 | import glob | |
@@ -73,23 +77,57 b' import shlex' | |||||
73 | import sys |
|
77 | import sys | |
74 | import types |
|
78 | import types | |
75 |
|
79 | |||
|
80 | import IPython.utils.rlineimpl as readline | |||
76 | from IPython.core.error import TryNext |
|
81 | from IPython.core.error import TryNext | |
77 | from IPython.core.prefilter import ESC_MAGIC |
|
82 | from IPython.core.prefilter import ESC_MAGIC | |
78 |
|
||||
79 | import IPython.utils.rlineimpl as readline |
|
|||
80 | from IPython.utils.ipstruct import Struct |
|
|||
81 | from IPython.utils import generics |
|
83 | from IPython.utils import generics | |
82 |
|
||||
83 | # Python 2.4 offers sets as a builtin |
|
|||
84 | try: |
|
|||
85 | set() |
|
|||
86 | except NameError: |
|
|||
87 | from sets import Set as set |
|
|||
88 |
|
||||
89 | from IPython.utils.genutils import debugx, dir2 |
|
84 | from IPython.utils.genutils import debugx, dir2 | |
90 |
|
85 | |||
|
86 | #----------------------------------------------------------------------------- | |||
|
87 | # Globals | |||
|
88 | #----------------------------------------------------------------------------- | |||
|
89 | ||||
|
90 | # Public API | |||
91 | __all__ = ['Completer','IPCompleter'] |
|
91 | __all__ = ['Completer','IPCompleter'] | |
92 |
|
92 | |||
|
93 | if sys.platform == 'win32': | |||
|
94 | PROTECTABLES = ' ' | |||
|
95 | else: | |||
|
96 | PROTECTABLES = ' ()' | |||
|
97 | ||||
|
98 | #----------------------------------------------------------------------------- | |||
|
99 | # Main functions and classes | |||
|
100 | #----------------------------------------------------------------------------- | |||
|
101 | ||||
|
102 | def protect_filename(s): | |||
|
103 | """Escape a string to protect certain characters.""" | |||
|
104 | ||||
|
105 | return "".join([(ch in PROTECTABLES and '\\' + ch or ch) | |||
|
106 | for ch in s]) | |||
|
107 | ||||
|
108 | ||||
|
109 | def single_dir_expand(matches): | |||
|
110 | "Recursively expand match lists containing a single dir." | |||
|
111 | ||||
|
112 | if len(matches) == 1 and os.path.isdir(matches[0]): | |||
|
113 | # Takes care of links to directories also. Use '/' | |||
|
114 | # explicitly, even under Windows, so that name completions | |||
|
115 | # don't end up escaped. | |||
|
116 | d = matches[0] | |||
|
117 | if d[-1] in ['/','\\']: | |||
|
118 | d = d[:-1] | |||
|
119 | ||||
|
120 | subdirs = os.listdir(d) | |||
|
121 | if subdirs: | |||
|
122 | matches = [ (d + '/' + p) for p in subdirs] | |||
|
123 | return single_dir_expand(matches) | |||
|
124 | else: | |||
|
125 | return matches | |||
|
126 | else: | |||
|
127 | return matches | |||
|
128 | ||||
|
129 | class Bunch: pass | |||
|
130 | ||||
93 | class Completer: |
|
131 | class Completer: | |
94 | def __init__(self,namespace=None,global_namespace=None): |
|
132 | def __init__(self,namespace=None,global_namespace=None): | |
95 | """Create a new completer for the command line. |
|
133 | """Create a new completer for the command line. | |
@@ -152,6 +190,7 b' class Completer:' | |||||
152 | defined in self.namespace or self.global_namespace that match. |
|
190 | defined in self.namespace or self.global_namespace that match. | |
153 |
|
191 | |||
154 | """ |
|
192 | """ | |
|
193 | #print 'Completer->global_matches, txt=%r' % text # dbg | |||
155 | matches = [] |
|
194 | matches = [] | |
156 | match_append = matches.append |
|
195 | match_append = matches.append | |
157 | n = len(text) |
|
196 | n = len(text) | |
@@ -179,6 +218,7 b' class Completer:' | |||||
179 | """ |
|
218 | """ | |
180 | import re |
|
219 | import re | |
181 |
|
220 | |||
|
221 | #print 'Completer->attr_matches, txt=%r' % text # dbg | |||
182 | # Another option, seems to work great. Catches things like ''.<tab> |
|
222 | # Another option, seems to work great. Catches things like ''.<tab> | |
183 | m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) |
|
223 | m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) | |
184 |
|
224 | |||
@@ -205,6 +245,7 b' class Completer:' | |||||
205 | res = ["%s.%s" % (expr, w) for w in words if w[:n] == attr ] |
|
245 | res = ["%s.%s" % (expr, w) for w in words if w[:n] == attr ] | |
206 | return res |
|
246 | return res | |
207 |
|
247 | |||
|
248 | ||||
208 | class IPCompleter(Completer): |
|
249 | class IPCompleter(Completer): | |
209 | """Extension of the completer class with IPython-specific features""" |
|
250 | """Extension of the completer class with IPython-specific features""" | |
210 |
|
251 | |||
@@ -235,7 +276,7 b' class IPCompleter(Completer):' | |||||
235 | to complete. """ |
|
276 | to complete. """ | |
236 |
|
277 | |||
237 | Completer.__init__(self,namespace,global_namespace) |
|
278 | Completer.__init__(self,namespace,global_namespace) | |
238 | self.magic_prefix = shell.name+'.magic_' |
|
279 | ||
239 | self.magic_escape = ESC_MAGIC |
|
280 | self.magic_escape = ESC_MAGIC | |
240 | self.readline = readline |
|
281 | self.readline = readline | |
241 | delims = self.readline.get_completer_delims() |
|
282 | delims = self.readline.get_completer_delims() | |
@@ -244,7 +285,8 b' class IPCompleter(Completer):' | |||||
244 | self.get_line_buffer = self.readline.get_line_buffer |
|
285 | self.get_line_buffer = self.readline.get_line_buffer | |
245 | self.get_endidx = self.readline.get_endidx |
|
286 | self.get_endidx = self.readline.get_endidx | |
246 | self.omit__names = omit__names |
|
287 | self.omit__names = omit__names | |
247 |
self.merge_completions = shell.readline_merge_completions |
|
288 | self.merge_completions = shell.readline_merge_completions | |
|
289 | self.shell = shell.shell | |||
248 | if alias_table is None: |
|
290 | if alias_table is None: | |
249 | alias_table = {} |
|
291 | alias_table = {} | |
250 | self.alias_table = alias_table |
|
292 | self.alias_table = alias_table | |
@@ -263,11 +305,13 b' class IPCompleter(Completer):' | |||||
263 | self.clean_glob = self._clean_glob_win32 |
|
305 | self.clean_glob = self._clean_glob_win32 | |
264 | else: |
|
306 | else: | |
265 | self.clean_glob = self._clean_glob |
|
307 | self.clean_glob = self._clean_glob | |
|
308 | ||||
|
309 | # All active matcher routines for completion | |||
266 | self.matchers = [self.python_matches, |
|
310 | self.matchers = [self.python_matches, | |
267 | self.file_matches, |
|
311 | self.file_matches, | |
|
312 | self.magic_matches, | |||
268 | self.alias_matches, |
|
313 | self.alias_matches, | |
269 | self.python_func_kw_matches] |
|
314 | self.python_func_kw_matches] | |
270 |
|
||||
271 |
|
315 | |||
272 | # Code contributed by Alex Schmolck, for ipython/emacs integration |
|
316 | # Code contributed by Alex Schmolck, for ipython/emacs integration | |
273 | def all_completions(self, text): |
|
317 | def all_completions(self, text): | |
@@ -278,9 +322,8 b' class IPCompleter(Completer):' | |||||
278 | try: |
|
322 | try: | |
279 | for i in xrange(sys.maxint): |
|
323 | for i in xrange(sys.maxint): | |
280 | res = self.complete(text, i) |
|
324 | res = self.complete(text, i) | |
281 |
|
325 | if not res: | ||
282 |
|
|
326 | break | |
283 |
|
||||
284 | comp_append(res) |
|
327 | comp_append(res) | |
285 | #XXX workaround for ``notDefined.<tab>`` |
|
328 | #XXX workaround for ``notDefined.<tab>`` | |
286 | except NameError: |
|
329 | except NameError: | |
@@ -316,41 +359,12 b' class IPCompleter(Completer):' | |||||
316 | # don't want to treat as delimiters in filename matching |
|
359 | # don't want to treat as delimiters in filename matching | |
317 | # when escaped with backslash |
|
360 | # when escaped with backslash | |
318 |
|
361 | |||
319 | if sys.platform == 'win32': |
|
|||
320 | protectables = ' ' |
|
|||
321 | else: |
|
|||
322 | protectables = ' ()' |
|
|||
323 |
|
||||
324 | if text.startswith('!'): |
|
362 | if text.startswith('!'): | |
325 | text = text[1:] |
|
363 | text = text[1:] | |
326 | text_prefix = '!' |
|
364 | text_prefix = '!' | |
327 | else: |
|
365 | else: | |
328 | text_prefix = '' |
|
366 | text_prefix = '' | |
329 |
|
367 | |||
330 | def protect_filename(s): |
|
|||
331 | return "".join([(ch in protectables and '\\' + ch or ch) |
|
|||
332 | for ch in s]) |
|
|||
333 |
|
||||
334 | def single_dir_expand(matches): |
|
|||
335 | "Recursively expand match lists containing a single dir." |
|
|||
336 |
|
||||
337 | if len(matches) == 1 and os.path.isdir(matches[0]): |
|
|||
338 | # Takes care of links to directories also. Use '/' |
|
|||
339 | # explicitly, even under Windows, so that name completions |
|
|||
340 | # don't end up escaped. |
|
|||
341 | d = matches[0] |
|
|||
342 | if d[-1] in ['/','\\']: |
|
|||
343 | d = d[:-1] |
|
|||
344 |
|
||||
345 | subdirs = os.listdir(d) |
|
|||
346 | if subdirs: |
|
|||
347 | matches = [ (d + '/' + p) for p in subdirs] |
|
|||
348 | return single_dir_expand(matches) |
|
|||
349 | else: |
|
|||
350 | return matches |
|
|||
351 | else: |
|
|||
352 | return matches |
|
|||
353 |
|
||||
354 | lbuf = self.lbuf |
|
368 | lbuf = self.lbuf | |
355 | open_quotes = 0 # track strings with open quotes |
|
369 | open_quotes = 0 # track strings with open quotes | |
356 | try: |
|
370 | try: | |
@@ -402,13 +416,24 b' class IPCompleter(Completer):' | |||||
402 | #print 'mm',matches # dbg |
|
416 | #print 'mm',matches # dbg | |
403 | return single_dir_expand(matches) |
|
417 | return single_dir_expand(matches) | |
404 |
|
418 | |||
|
419 | def magic_matches(self, text): | |||
|
420 | """Match magics""" | |||
|
421 | #print 'Completer->magic_matches:',text,'lb',self.lbuf # dbg | |||
|
422 | # Get all shell magics now rather than statically, so magics loaded at | |||
|
423 | # runtime show up too | |||
|
424 | magics = self.shell.lsmagic() | |||
|
425 | pre = self.magic_escape | |||
|
426 | baretext = text.lstrip(pre) | |||
|
427 | return [ pre+m for m in magics if m.startswith(baretext)] | |||
|
428 | ||||
405 | def alias_matches(self, text): |
|
429 | def alias_matches(self, text): | |
406 | """Match internal system aliases""" |
|
430 | """Match internal system aliases""" | |
407 | #print 'Completer->alias_matches:',text,'lb',self.lbuf # dbg |
|
431 | #print 'Completer->alias_matches:',text,'lb',self.lbuf # dbg | |
408 |
|
432 | |||
409 | # if we are not in the first 'item', alias matching |
|
433 | # if we are not in the first 'item', alias matching | |
410 | # doesn't make sense - unless we are starting with 'sudo' command. |
|
434 | # doesn't make sense - unless we are starting with 'sudo' command. | |
411 |
if ' ' in self.lbuf.lstrip() and |
|
435 | if ' ' in self.lbuf.lstrip() and \ | |
|
436 | not self.lbuf.lstrip().startswith('sudo'): | |||
412 | return [] |
|
437 | return [] | |
413 | text = os.path.expanduser(text) |
|
438 | text = os.path.expanduser(text) | |
414 | aliases = self.alias_table.keys() |
|
439 | aliases = self.alias_table.keys() | |
@@ -420,7 +445,7 b' class IPCompleter(Completer):' | |||||
420 | def python_matches(self,text): |
|
445 | def python_matches(self,text): | |
421 | """Match attributes or global python names""" |
|
446 | """Match attributes or global python names""" | |
422 |
|
447 | |||
423 |
#print 'Completer->python_matches, txt= |
|
448 | #print 'Completer->python_matches, txt=%r' % text # dbg | |
424 | if "." in text: |
|
449 | if "." in text: | |
425 | try: |
|
450 | try: | |
426 | matches = self.attr_matches(text) |
|
451 | matches = self.attr_matches(text) | |
@@ -439,11 +464,7 b' class IPCompleter(Completer):' | |||||
439 | matches = [] |
|
464 | matches = [] | |
440 | else: |
|
465 | else: | |
441 | matches = self.global_matches(text) |
|
466 | matches = self.global_matches(text) | |
442 | # this is so completion finds magics when automagic is on: |
|
467 | ||
443 | if (matches == [] and |
|
|||
444 | not text.startswith(os.sep) and |
|
|||
445 | not ' ' in self.lbuf): |
|
|||
446 | matches = self.attr_matches(self.magic_prefix+text) |
|
|||
447 | return matches |
|
468 | return matches | |
448 |
|
469 | |||
449 | def _default_arguments(self, obj): |
|
470 | def _default_arguments(self, obj): | |
@@ -514,9 +535,11 b' class IPCompleter(Completer):' | |||||
514 | callableMatches = self.attr_matches('.'.join(ids[::-1])) |
|
535 | callableMatches = self.attr_matches('.'.join(ids[::-1])) | |
515 | argMatches = [] |
|
536 | argMatches = [] | |
516 | for callableMatch in callableMatches: |
|
537 | for callableMatch in callableMatches: | |
517 | try: namedArgs = self._default_arguments(eval(callableMatch, |
|
538 | try: | |
|
539 | namedArgs = self._default_arguments(eval(callableMatch, | |||
518 | self.namespace)) |
|
540 | self.namespace)) | |
519 |
except: |
|
541 | except: | |
|
542 | continue | |||
520 | for namedArg in namedArgs: |
|
543 | for namedArg in namedArgs: | |
521 | if namedArg.startswith(text): |
|
544 | if namedArg.startswith(text): | |
522 | argMatches.append("%s=" %namedArg) |
|
545 | argMatches.append("%s=" %namedArg) | |
@@ -528,7 +551,7 b' class IPCompleter(Completer):' | |||||
528 | if not line.strip(): |
|
551 | if not line.strip(): | |
529 | return None |
|
552 | return None | |
530 |
|
553 | |||
531 |
event = |
|
554 | event = Bunch() | |
532 | event.line = line |
|
555 | event.line = line | |
533 | event.symbol = text |
|
556 | event.symbol = text | |
534 | cmd = line.split(None,1)[0] |
|
557 | cmd = line.split(None,1)[0] | |
@@ -540,11 +563,9 b' class IPCompleter(Completer):' | |||||
540 | try_magic = self.custom_completers.s_matches( |
|
563 | try_magic = self.custom_completers.s_matches( | |
541 | self.magic_escape + cmd) |
|
564 | self.magic_escape + cmd) | |
542 | else: |
|
565 | else: | |
543 | try_magic = [] |
|
566 | try_magic = [] | |
544 |
|
||||
545 |
|
567 | |||
546 | for c in itertools.chain( |
|
568 | for c in itertools.chain(self.custom_completers.s_matches(cmd), | |
547 | self.custom_completers.s_matches(cmd), |
|
|||
548 | try_magic, |
|
569 | try_magic, | |
549 | self.custom_completers.flat_matches(self.lbuf)): |
|
570 | self.custom_completers.flat_matches(self.lbuf)): | |
550 | #print "try",c # dbg |
|
571 | #print "try",c # dbg | |
@@ -555,7 +576,8 b' class IPCompleter(Completer):' | |||||
555 | if withcase: |
|
576 | if withcase: | |
556 | return withcase |
|
577 | return withcase | |
557 | # if none, then case insensitive ones are ok too |
|
578 | # if none, then case insensitive ones are ok too | |
558 | return [r for r in res if r.lower().startswith(text.lower())] |
|
579 | text_low = text.lower() | |
|
580 | return [r for r in res if r.lower().startswith(text_low)] | |||
559 | except TryNext: |
|
581 | except TryNext: | |
560 | pass |
|
582 | pass | |
561 |
|
583 | |||
@@ -598,14 +620,11 b' class IPCompleter(Completer):' | |||||
598 | return None |
|
620 | return None | |
599 |
|
621 | |||
600 | magic_escape = self.magic_escape |
|
622 | magic_escape = self.magic_escape | |
601 | magic_prefix = self.magic_prefix |
|
|||
602 |
|
623 | |||
603 | self.lbuf = self.full_lbuf[:self.get_endidx()] |
|
624 | self.lbuf = self.full_lbuf[:self.get_endidx()] | |
604 |
|
625 | |||
605 | try: |
|
626 | try: | |
606 |
if text.startswith( |
|
627 | if text.startswith('~'): | |
607 | text = text.replace(magic_escape,magic_prefix) |
|
|||
608 | elif text.startswith('~'): |
|
|||
609 | text = os.path.expanduser(text) |
|
628 | text = os.path.expanduser(text) | |
610 | if state == 0: |
|
629 | if state == 0: | |
611 | custom_res = self.dispatch_custom_completer(text) |
|
630 | custom_res = self.dispatch_custom_completer(text) | |
@@ -625,13 +644,10 b' class IPCompleter(Completer):' | |||||
625 | self.matches = matcher(text) |
|
644 | self.matches = matcher(text) | |
626 | if self.matches: |
|
645 | if self.matches: | |
627 | break |
|
646 | break | |
628 | def uniq(alist): |
|
647 | self.matches = list(set(self.matches)) | |
629 | set = {} |
|
|||
630 | return [set.setdefault(e,e) for e in alist if e not in set] |
|
|||
631 | self.matches = uniq(self.matches) |
|
|||
632 | try: |
|
648 | try: | |
633 | ret = self.matches[state].replace(magic_prefix,magic_escape) |
|
649 | #print "MATCH: %r" % self.matches[state] # dbg | |
634 |
return |
|
650 | return self.matches[state] | |
635 | except IndexError: |
|
651 | except IndexError: | |
636 | return None |
|
652 | return None | |
637 | except: |
|
653 | except: |
General Comments 0
You need to be logged in to leave comments.
Login now