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 | 45 | - When the original stdin is not a tty device, GNU readline is never |
|
46 | 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 | 53 | # proper procedure is to maintain its copyright as belonging to the Python |
|
55 | 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 | 58 | # Copyright (C) 2001 Python Software Foundation, www.python.org |
|
58 | # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu> | |
|
59 | 59 | # |
|
60 | 60 | # Distributed under the terms of the BSD License. The full license is in |
|
61 | 61 | # the file COPYING, distributed as part of this software. |
|
62 | 62 | # |
|
63 | 63 | #***************************************************************************** |
|
64 | 64 | |
|
65 | #----------------------------------------------------------------------------- | |
|
66 | # Imports | |
|
67 | #----------------------------------------------------------------------------- | |
|
68 | ||
|
65 | 69 | import __builtin__ |
|
66 | 70 | import __main__ |
|
67 | 71 | import glob |
@@ -73,23 +77,57 b' import shlex' | |||
|
73 | 77 | import sys |
|
74 | 78 | import types |
|
75 | 79 | |
|
80 | import IPython.utils.rlineimpl as readline | |
|
76 | 81 | from IPython.core.error import TryNext |
|
77 | 82 | from IPython.core.prefilter import ESC_MAGIC |
|
78 | ||
|
79 | import IPython.utils.rlineimpl as readline | |
|
80 | from IPython.utils.ipstruct import Struct | |
|
81 | 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 | 84 | from IPython.utils.genutils import debugx, dir2 |
|
90 | 85 | |
|
86 | #----------------------------------------------------------------------------- | |
|
87 | # Globals | |
|
88 | #----------------------------------------------------------------------------- | |
|
89 | ||
|
90 | # Public API | |
|
91 | 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 | 131 | class Completer: |
|
94 | 132 | def __init__(self,namespace=None,global_namespace=None): |
|
95 | 133 | """Create a new completer for the command line. |
@@ -152,6 +190,7 b' class Completer:' | |||
|
152 | 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 | 194 | matches = [] |
|
156 | 195 | match_append = matches.append |
|
157 | 196 | n = len(text) |
@@ -179,6 +218,7 b' class Completer:' | |||
|
179 | 218 | """ |
|
180 | 219 | import re |
|
181 | 220 | |
|
221 | #print 'Completer->attr_matches, txt=%r' % text # dbg | |
|
182 | 222 | # Another option, seems to work great. Catches things like ''.<tab> |
|
183 | 223 | m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) |
|
184 | 224 | |
@@ -205,6 +245,7 b' class Completer:' | |||
|
205 | 245 | res = ["%s.%s" % (expr, w) for w in words if w[:n] == attr ] |
|
206 | 246 | return res |
|
207 | 247 | |
|
248 | ||
|
208 | 249 | class IPCompleter(Completer): |
|
209 | 250 | """Extension of the completer class with IPython-specific features""" |
|
210 | 251 | |
@@ -235,7 +276,7 b' class IPCompleter(Completer):' | |||
|
235 | 276 | to complete. """ |
|
236 | 277 | |
|
237 | 278 | Completer.__init__(self,namespace,global_namespace) |
|
238 | self.magic_prefix = shell.name+'.magic_' | |
|
279 | ||
|
239 | 280 | self.magic_escape = ESC_MAGIC |
|
240 | 281 | self.readline = readline |
|
241 | 282 | delims = self.readline.get_completer_delims() |
@@ -244,7 +285,8 b' class IPCompleter(Completer):' | |||
|
244 | 285 | self.get_line_buffer = self.readline.get_line_buffer |
|
245 | 286 | self.get_endidx = self.readline.get_endidx |
|
246 | 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 | 290 | if alias_table is None: |
|
249 | 291 | alias_table = {} |
|
250 | 292 | self.alias_table = alias_table |
@@ -263,11 +305,13 b' class IPCompleter(Completer):' | |||
|
263 | 305 | self.clean_glob = self._clean_glob_win32 |
|
264 | 306 | else: |
|
265 | 307 | self.clean_glob = self._clean_glob |
|
308 | ||
|
309 | # All active matcher routines for completion | |
|
266 | 310 | self.matchers = [self.python_matches, |
|
267 | 311 | self.file_matches, |
|
312 | self.magic_matches, | |
|
268 | 313 | self.alias_matches, |
|
269 | 314 | self.python_func_kw_matches] |
|
270 | ||
|
271 | 315 | |
|
272 | 316 | # Code contributed by Alex Schmolck, for ipython/emacs integration |
|
273 | 317 | def all_completions(self, text): |
@@ -278,9 +322,8 b' class IPCompleter(Completer):' | |||
|
278 | 322 | try: |
|
279 | 323 | for i in xrange(sys.maxint): |
|
280 | 324 | res = self.complete(text, i) |
|
281 | ||
|
282 |
|
|
|
283 | ||
|
325 | if not res: | |
|
326 | break | |
|
284 | 327 | comp_append(res) |
|
285 | 328 | #XXX workaround for ``notDefined.<tab>`` |
|
286 | 329 | except NameError: |
@@ -316,41 +359,12 b' class IPCompleter(Completer):' | |||
|
316 | 359 | # don't want to treat as delimiters in filename matching |
|
317 | 360 | # when escaped with backslash |
|
318 | 361 | |
|
319 | if sys.platform == 'win32': | |
|
320 | protectables = ' ' | |
|
321 | else: | |
|
322 | protectables = ' ()' | |
|
323 | ||
|
324 | 362 | if text.startswith('!'): |
|
325 | 363 | text = text[1:] |
|
326 | 364 | text_prefix = '!' |
|
327 | 365 | else: |
|
328 | 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 | 368 | lbuf = self.lbuf |
|
355 | 369 | open_quotes = 0 # track strings with open quotes |
|
356 | 370 | try: |
@@ -402,13 +416,24 b' class IPCompleter(Completer):' | |||
|
402 | 416 | #print 'mm',matches # dbg |
|
403 | 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 | 429 | def alias_matches(self, text): |
|
406 | 430 | """Match internal system aliases""" |
|
407 | 431 | #print 'Completer->alias_matches:',text,'lb',self.lbuf # dbg |
|
408 | 432 | |
|
409 | 433 | # if we are not in the first 'item', alias matching |
|
410 | 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 | 437 | return [] |
|
413 | 438 | text = os.path.expanduser(text) |
|
414 | 439 | aliases = self.alias_table.keys() |
@@ -420,7 +445,7 b' class IPCompleter(Completer):' | |||
|
420 | 445 | def python_matches(self,text): |
|
421 | 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 | 449 | if "." in text: |
|
425 | 450 | try: |
|
426 | 451 | matches = self.attr_matches(text) |
@@ -439,11 +464,7 b' class IPCompleter(Completer):' | |||
|
439 | 464 | matches = [] |
|
440 | 465 | else: |
|
441 | 466 | matches = self.global_matches(text) |
|
442 | # this is so completion finds magics when automagic is on: | |
|
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) | |
|
467 | ||
|
447 | 468 | return matches |
|
448 | 469 | |
|
449 | 470 | def _default_arguments(self, obj): |
@@ -514,9 +535,11 b' class IPCompleter(Completer):' | |||
|
514 | 535 | callableMatches = self.attr_matches('.'.join(ids[::-1])) |
|
515 | 536 | argMatches = [] |
|
516 | 537 | for callableMatch in callableMatches: |
|
517 | try: namedArgs = self._default_arguments(eval(callableMatch, | |
|
538 | try: | |
|
539 | namedArgs = self._default_arguments(eval(callableMatch, | |
|
518 | 540 | self.namespace)) |
|
519 |
except: |
|
|
541 | except: | |
|
542 | continue | |
|
520 | 543 | for namedArg in namedArgs: |
|
521 | 544 | if namedArg.startswith(text): |
|
522 | 545 | argMatches.append("%s=" %namedArg) |
@@ -528,7 +551,7 b' class IPCompleter(Completer):' | |||
|
528 | 551 | if not line.strip(): |
|
529 | 552 | return None |
|
530 | 553 | |
|
531 |
event = |
|
|
554 | event = Bunch() | |
|
532 | 555 | event.line = line |
|
533 | 556 | event.symbol = text |
|
534 | 557 | cmd = line.split(None,1)[0] |
@@ -540,11 +563,9 b' class IPCompleter(Completer):' | |||
|
540 | 563 | try_magic = self.custom_completers.s_matches( |
|
541 | 564 | self.magic_escape + cmd) |
|
542 | 565 | else: |
|
543 | try_magic = [] | |
|
544 | ||
|
566 | try_magic = [] | |
|
545 | 567 | |
|
546 | for c in itertools.chain( | |
|
547 | self.custom_completers.s_matches(cmd), | |
|
568 | for c in itertools.chain(self.custom_completers.s_matches(cmd), | |
|
548 | 569 | try_magic, |
|
549 | 570 | self.custom_completers.flat_matches(self.lbuf)): |
|
550 | 571 | #print "try",c # dbg |
@@ -555,7 +576,8 b' class IPCompleter(Completer):' | |||
|
555 | 576 | if withcase: |
|
556 | 577 | return withcase |
|
557 | 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 | 581 | except TryNext: |
|
560 | 582 | pass |
|
561 | 583 | |
@@ -598,14 +620,11 b' class IPCompleter(Completer):' | |||
|
598 | 620 | return None |
|
599 | 621 | |
|
600 | 622 | magic_escape = self.magic_escape |
|
601 | magic_prefix = self.magic_prefix | |
|
602 | 623 | |
|
603 | 624 | self.lbuf = self.full_lbuf[:self.get_endidx()] |
|
604 | 625 | |
|
605 | 626 | try: |
|
606 |
if text.startswith( |
|
|
607 | text = text.replace(magic_escape,magic_prefix) | |
|
608 | elif text.startswith('~'): | |
|
627 | if text.startswith('~'): | |
|
609 | 628 | text = os.path.expanduser(text) |
|
610 | 629 | if state == 0: |
|
611 | 630 | custom_res = self.dispatch_custom_completer(text) |
@@ -625,13 +644,10 b' class IPCompleter(Completer):' | |||
|
625 | 644 | self.matches = matcher(text) |
|
626 | 645 | if self.matches: |
|
627 | 646 | break |
|
628 | def uniq(alist): | |
|
629 | set = {} | |
|
630 | return [set.setdefault(e,e) for e in alist if e not in set] | |
|
631 | self.matches = uniq(self.matches) | |
|
647 | self.matches = list(set(self.matches)) | |
|
632 | 648 | try: |
|
633 | ret = self.matches[state].replace(magic_prefix,magic_escape) | |
|
634 |
return |
|
|
649 | #print "MATCH: %r" % self.matches[state] # dbg | |
|
650 | return self.matches[state] | |
|
635 | 651 | except IndexError: |
|
636 | 652 | return None |
|
637 | 653 | except: |
General Comments 0
You need to be logged in to leave comments.
Login now