##// END OF EJS Templates
Merge pull request #5790 from minrk/dict-completion-bug...
Min RK -
r16685:811d52cc merge
parent child Browse files
Show More
@@ -79,6 +79,7 from IPython.config.configurable import Configurable
79 from IPython.core.error import TryNext
79 from IPython.core.error import TryNext
80 from IPython.core.inputsplitter import ESC_MAGIC
80 from IPython.core.inputsplitter import ESC_MAGIC
81 from IPython.utils import generics
81 from IPython.utils import generics
82 from IPython.utils import io
82 from IPython.utils.dir2 import dir2
83 from IPython.utils.dir2 import dir2
83 from IPython.utils.process import arg_split
84 from IPython.utils.process import arg_split
84 from IPython.utils.py3compat import builtin_mod, string_types
85 from IPython.utils.py3compat import builtin_mod, string_types
@@ -429,16 +430,18 def get__all__entries(obj):
429 def match_dict_keys(keys, prefix):
430 def match_dict_keys(keys, prefix):
430 """Used by dict_key_matches, matching the prefix to a list of keys"""
431 """Used by dict_key_matches, matching the prefix to a list of keys"""
431 if not prefix:
432 if not prefix:
432 return None, [repr(k) for k in keys
433 return None, 0, [repr(k) for k in keys
433 if isinstance(k, (string_types, bytes))]
434 if isinstance(k, (string_types, bytes))]
434 quote_match = re.search('["\']', prefix)
435 quote_match = re.search('["\']', prefix)
435 quote = quote_match.group()
436 quote = quote_match.group()
436 try:
437 try:
437 prefix_str = eval(prefix + quote, {})
438 prefix_str = eval(prefix + quote, {})
438 except Exception:
439 except Exception:
439 return None, []
440 return None, 0, []
440
441
441 token_prefix = re.search('\w*$', prefix).group()
442 token_match = re.search(r'\w*$', prefix, re.UNICODE)
443 token_start = token_match.start()
444 token_prefix = token_match.group()
442
445
443 # TODO: support bytes in Py3k
446 # TODO: support bytes in Py3k
444 matched = []
447 matched = []
@@ -471,7 +474,7 def match_dict_keys(keys, prefix):
471
474
472 # then reinsert prefix from start of token
475 # then reinsert prefix from start of token
473 matched.append('%s%s' % (token_prefix, rem_repr))
476 matched.append('%s%s' % (token_prefix, rem_repr))
474 return quote, matched
477 return quote, token_start, matched
475
478
476
479
477 def _safe_isinstance(obj, module, class_name):
480 def _safe_isinstance(obj, module, class_name):
@@ -919,17 +922,47 class IPCompleter(Completer):
919 keys = get_keys(obj)
922 keys = get_keys(obj)
920 if not keys:
923 if not keys:
921 return keys
924 return keys
922 closing_quote, matches = match_dict_keys(keys, prefix)
925 closing_quote, token_offset, matches = match_dict_keys(keys, prefix)
926 if not matches:
927 return matches
928
929 # get the cursor position of
930 # - the text being completed
931 # - the start of the key text
932 # - the start of the completion
933 text_start = len(self.text_until_cursor) - len(text)
934 if prefix:
935 key_start = match.start(2)
936 completion_start = key_start + token_offset
937 else:
938 key_start = completion_start = match.end()
939
940 # grab the leading prefix, to make sure all completions start with `text`
941 if text_start > key_start:
942 leading = ''
943 else:
944 leading = text[text_start:completion_start]
945
946 # the index of the `[` character
947 bracket_idx = match.end(1)
923
948
924 # append closing quote and bracket as appropriate
949 # append closing quote and bracket as appropriate
950 # this is *not* appropriate if the opening quote or bracket is outside
951 # the text given to this method
952 suf = ''
925 continuation = self.line_buffer[len(self.text_until_cursor):]
953 continuation = self.line_buffer[len(self.text_until_cursor):]
926 if closing_quote and continuation.startswith(closing_quote):
954 if key_start > text_start and closing_quote:
927 suf = ''
955 # quotes were opened inside text, maybe close them
928 elif continuation.startswith(']'):
956 if continuation.startswith(closing_quote):
929 suf = closing_quote or ''
957 continuation = continuation[len(closing_quote):]
930 else:
958 else:
931 suf = (closing_quote or '') + ']'
959 suf += closing_quote
932 return [k + suf for k in matches]
960 if bracket_idx > text_start:
961 # brackets were opened inside text, maybe close them
962 if not continuation.startswith(']'):
963 suf += ']'
964
965 return [leading + k + suf for k in matches]
933
966
934 def dispatch_custom_completer(self, text):
967 def dispatch_custom_completer(self, text):
935 #io.rprint("Custom! '%s' %s" % (text, self.custom_completers)) # dbg
968 #io.rprint("Custom! '%s' %s" % (text, self.custom_completers)) # dbg
@@ -1,18 +1,17
1 """Tests for the IPython tab-completion machinery.
1 # encoding: utf-8
2 """
2 """Tests for the IPython tab-completion machinery."""
3 #-----------------------------------------------------------------------------
3
4 # Module imports
4 # Copyright (c) IPython Development Team.
5 #-----------------------------------------------------------------------------
5 # Distributed under the terms of the Modified BSD License.
6
6
7 # stdlib
8 import os
7 import os
9 import sys
8 import sys
10 import unittest
9 import unittest
11
10
12 # third party
11 from contextlib import contextmanager
12
13 import nose.tools as nt
13 import nose.tools as nt
14
14
15 # our own packages
16 from IPython.config.loader import Config
15 from IPython.config.loader import Config
17 from IPython.core import completer
16 from IPython.core import completer
18 from IPython.external.decorators import knownfailureif
17 from IPython.external.decorators import knownfailureif
@@ -25,6 +24,17 from IPython.testing import decorators as dec
25 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
26 # Test functions
25 # Test functions
27 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
28 @contextmanager
29 def greedy_completion():
30 ip = get_ipython()
31 greedy_original = ip.Completer.greedy
32 try:
33 ip.Completer.greedy = True
34 yield
35 finally:
36 ip.Completer.greedy = greedy_original
37
28 def test_protect_filename():
38 def test_protect_filename():
29 pairs = [ ('abc','abc'),
39 pairs = [ ('abc','abc'),
30 (' abc',r'\ abc'),
40 (' abc',r'\ abc'),
@@ -205,18 +215,13 def test_local_file_completions():
205
215
206 def test_greedy_completions():
216 def test_greedy_completions():
207 ip = get_ipython()
217 ip = get_ipython()
208 greedy_original = ip.Completer.greedy
218 ip.ex('a=list(range(5))')
209 try:
219 _,c = ip.complete('.',line='a[0].')
210 ip.Completer.greedy = False
220 nt.assert_false('a[0].real' in c,
211 ip.ex('a=list(range(5))')
221 "Shouldn't have completed on a[0]: %s"%c)
212 _,c = ip.complete('.',line='a[0].')
222 with greedy_completion():
213 nt.assert_false('a[0].real' in c,
214 "Shouldn't have completed on a[0]: %s"%c)
215 ip.Completer.greedy = True
216 _,c = ip.complete('.',line='a[0].')
223 _,c = ip.complete('.',line='a[0].')
217 nt.assert_true('a[0].real' in c, "Should have completed on a[0]: %s"%c)
224 nt.assert_true('a[0].real' in c, "Should have completed on a[0]: %s"%c)
218 finally:
219 ip.Completer.greedy = greedy_original
220
225
221
226
222 def test_omit__names():
227 def test_omit__names():
@@ -394,7 +399,6 def test_magic_completion_order():
394 nt.assert_equal(matches, ["timeit", "%timeit","%%timeit"])
399 nt.assert_equal(matches, ["timeit", "%timeit","%%timeit"])
395
400
396
401
397
398 def test_dict_key_completion_string():
402 def test_dict_key_completion_string():
399 """Test dictionary key completion for string keys"""
403 """Test dictionary key completion for string keys"""
400 ip = get_ipython()
404 ip = get_ipython()
@@ -404,20 +408,25 def test_dict_key_completion_string():
404
408
405 # check completion at different stages
409 # check completion at different stages
406 _, matches = complete(line_buffer="d[")
410 _, matches = complete(line_buffer="d[")
407 nt.assert_in("'abc']", matches)
411 nt.assert_in("'abc'", matches)
412 nt.assert_not_in("'abc']", matches)
408
413
409 _, matches = complete(line_buffer="d['")
414 _, matches = complete(line_buffer="d['")
410 nt.assert_in("abc']", matches)
415 nt.assert_in("abc", matches)
416 nt.assert_not_in("abc']", matches)
411
417
412 _, matches = complete(line_buffer="d['a")
418 _, matches = complete(line_buffer="d['a")
413 nt.assert_in("abc']", matches)
419 nt.assert_in("abc", matches)
420 nt.assert_not_in("abc']", matches)
414
421
415 # check use of different quoting
422 # check use of different quoting
416 _, matches = complete(line_buffer="d[\"")
423 _, matches = complete(line_buffer="d[\"")
417 nt.assert_in("abc\"]", matches)
424 nt.assert_in("abc", matches)
425 nt.assert_not_in('abc\"]', matches)
418
426
419 _, matches = complete(line_buffer="d[\"a")
427 _, matches = complete(line_buffer="d[\"a")
420 nt.assert_in("abc\"]", matches)
428 nt.assert_in("abc", matches)
429 nt.assert_not_in('abc\"]', matches)
421
430
422 # check sensitivity to following context
431 # check sensitivity to following context
423 _, matches = complete(line_buffer="d[]", cursor_pos=2)
432 _, matches = complete(line_buffer="d[]", cursor_pos=2)
@@ -425,39 +434,43 def test_dict_key_completion_string():
425
434
426 _, matches = complete(line_buffer="d['']", cursor_pos=3)
435 _, matches = complete(line_buffer="d['']", cursor_pos=3)
427 nt.assert_in("abc", matches)
436 nt.assert_in("abc", matches)
437 nt.assert_not_in("abc'", matches)
438 nt.assert_not_in("abc']", matches)
428
439
429 # check multiple solutions are correctly returned and that noise is not
440 # check multiple solutions are correctly returned and that noise is not
430 ip.user_ns['d'] = {'abc': None, 'abd': None, 'bad': None, object(): None,
441 ip.user_ns['d'] = {'abc': None, 'abd': None, 'bad': None, object(): None,
431 5: None}
442 5: None}
432
443
433 _, matches = complete(line_buffer="d['a")
444 _, matches = complete(line_buffer="d['a")
434 nt.assert_in("abc']", matches)
445 nt.assert_in("abc", matches)
435 nt.assert_in("abd']", matches)
446 nt.assert_in("abd", matches)
436 nt.assert_not_in("bad']", matches)
447 nt.assert_not_in("bad", matches)
448 assert not any(m.endswith((']', '"', "'")) for m in matches), matches
437
449
438 # check escaping and whitespace
450 # check escaping and whitespace
439 ip.user_ns['d'] = {'a\nb': None, 'a\'b': None, 'a"b': None, 'a word': None}
451 ip.user_ns['d'] = {'a\nb': None, 'a\'b': None, 'a"b': None, 'a word': None}
440 _, matches = complete(line_buffer="d['a")
452 _, matches = complete(line_buffer="d['a")
441 nt.assert_in("a\\nb']", matches)
453 nt.assert_in("a\\nb", matches)
442 nt.assert_in("a\\'b']", matches)
454 nt.assert_in("a\\'b", matches)
443 nt.assert_in("a\"b']", matches)
455 nt.assert_in("a\"b", matches)
444 nt.assert_in("a word']", matches)
456 nt.assert_in("a word", matches)
457 assert not any(m.endswith((']', '"', "'")) for m in matches), matches
445
458
446 # - can complete on non-initial word of the string
459 # - can complete on non-initial word of the string
447 _, matches = complete(line_buffer="d['a w")
460 _, matches = complete(line_buffer="d['a w")
448 nt.assert_in("word']", matches)
461 nt.assert_in("word", matches)
449
462
450 # - understands quote escaping
463 # - understands quote escaping
451 _, matches = complete(line_buffer="d['a\\'")
464 _, matches = complete(line_buffer="d['a\\'")
452 nt.assert_in("b']", matches)
465 nt.assert_in("b", matches)
453
466
454 # - default quoting should work like repr
467 # - default quoting should work like repr
455 _, matches = complete(line_buffer="d[")
468 _, matches = complete(line_buffer="d[")
456 nt.assert_in("\"a'b\"]", matches)
469 nt.assert_in("\"a'b\"", matches)
457
470
458 # - when opening quote with ", possible to match with unescaped apostrophe
471 # - when opening quote with ", possible to match with unescaped apostrophe
459 _, matches = complete(line_buffer="d[\"a'")
472 _, matches = complete(line_buffer="d[\"a'")
460 nt.assert_in("b\"]", matches)
473 nt.assert_in("b", matches)
461
474
462
475
463 def test_dict_key_completion_contexts():
476 def test_dict_key_completion_contexts():
@@ -482,10 +495,10 def test_dict_key_completion_contexts():
482
495
483 def assert_completion(**kwargs):
496 def assert_completion(**kwargs):
484 _, matches = complete(**kwargs)
497 _, matches = complete(**kwargs)
485 nt.assert_in("'abc']", matches)
498 nt.assert_in("'abc'", matches)
499 nt.assert_not_in("'abc']", matches)
486
500
487 # no completion after string closed, even if reopened
501 # no completion after string closed, even if reopened
488 ip.Completer.greedy = False
489 assert_no_completion(line_buffer="d['a'")
502 assert_no_completion(line_buffer="d['a'")
490 assert_no_completion(line_buffer="d[\"a\"")
503 assert_no_completion(line_buffer="d[\"a\"")
491 assert_no_completion(line_buffer="d['a' + ")
504 assert_no_completion(line_buffer="d['a' + ")
@@ -497,9 +510,17 def test_dict_key_completion_contexts():
497 assert_completion(line_buffer="C.data[")
510 assert_completion(line_buffer="C.data[")
498
511
499 # greedy flag
512 # greedy flag
513 def assert_completion(**kwargs):
514 _, matches = complete(**kwargs)
515 nt.assert_in("get()['abc']", matches)
516
500 assert_no_completion(line_buffer="get()[")
517 assert_no_completion(line_buffer="get()[")
501 ip.Completer.greedy = True
518 with greedy_completion():
502 assert_completion(line_buffer="get()[")
519 assert_completion(line_buffer="get()[")
520 assert_completion(line_buffer="get()['")
521 assert_completion(line_buffer="get()['a")
522 assert_completion(line_buffer="get()['ab")
523 assert_completion(line_buffer="get()['abc")
503
524
504
525
505
526
@@ -512,25 +533,25 def test_dict_key_completion_bytes():
512 ip.user_ns['d'] = {'abc': None, b'abd': None}
533 ip.user_ns['d'] = {'abc': None, b'abd': None}
513
534
514 _, matches = complete(line_buffer="d[")
535 _, matches = complete(line_buffer="d[")
515 nt.assert_in("'abc']", matches)
536 nt.assert_in("'abc'", matches)
516 nt.assert_in("b'abd']", matches)
537 nt.assert_in("b'abd'", matches)
517
538
518 if False: # not currently implemented
539 if False: # not currently implemented
519 _, matches = complete(line_buffer="d[b")
540 _, matches = complete(line_buffer="d[b")
520 nt.assert_in("b'abd']", matches)
541 nt.assert_in("b'abd'", matches)
521 nt.assert_not_in("b'abc']", matches)
542 nt.assert_not_in("b'abc'", matches)
522
543
523 _, matches = complete(line_buffer="d[b'")
544 _, matches = complete(line_buffer="d[b'")
524 nt.assert_in("abd']", matches)
545 nt.assert_in("abd", matches)
525 nt.assert_not_in("abc']", matches)
546 nt.assert_not_in("abc", matches)
526
547
527 _, matches = complete(line_buffer="d[B'")
548 _, matches = complete(line_buffer="d[B'")
528 nt.assert_in("abd']", matches)
549 nt.assert_in("abd", matches)
529 nt.assert_not_in("abc']", matches)
550 nt.assert_not_in("abc", matches)
530
551
531 _, matches = complete(line_buffer="d['")
552 _, matches = complete(line_buffer="d['")
532 nt.assert_in("abc']", matches)
553 nt.assert_in("abc", matches)
533 nt.assert_not_in("abd']", matches)
554 nt.assert_not_in("abd", matches)
534
555
535
556
536 @dec.onlyif(sys.version_info[0] < 3, 'This test only applies in Py<3')
557 @dec.onlyif(sys.version_info[0] < 3, 'This test only applies in Py<3')
@@ -540,31 +561,56 def test_dict_key_completion_unicode_py2():
540 complete = ip.Completer.complete
561 complete = ip.Completer.complete
541
562
542 ip.user_ns['d'] = {u'abc': None,
563 ip.user_ns['d'] = {u'abc': None,
543 unicode_type('a\xd7\x90', 'utf8'): None}
564 u'a\u05d0b': None}
544
565
545 _, matches = complete(line_buffer="d[")
566 _, matches = complete(line_buffer="d[")
546 nt.assert_in("u'abc']", matches)
567 nt.assert_in("u'abc'", matches)
547 nt.assert_in("u'a\\u05d0']", matches)
568 nt.assert_in("u'a\\u05d0b'", matches)
548
569
549 _, matches = complete(line_buffer="d['a")
570 _, matches = complete(line_buffer="d['a")
550 nt.assert_in("abc']", matches)
571 nt.assert_in("abc", matches)
551 nt.assert_not_in("a\\u05d0']", matches)
572 nt.assert_not_in("a\\u05d0b", matches)
552
573
553 _, matches = complete(line_buffer="d[u'a")
574 _, matches = complete(line_buffer="d[u'a")
554 nt.assert_in("abc']", matches)
575 nt.assert_in("abc", matches)
555 nt.assert_in("a\\u05d0']", matches)
576 nt.assert_in("a\\u05d0b", matches)
556
577
557 _, matches = complete(line_buffer="d[U'a")
578 _, matches = complete(line_buffer="d[U'a")
558 nt.assert_in("abc']", matches)
579 nt.assert_in("abc", matches)
559 nt.assert_in("a\\u05d0']", matches)
580 nt.assert_in("a\\u05d0b", matches)
560
581
561 # query using escape
582 # query using escape
562 _, matches = complete(line_buffer="d[u'a\\u05d0")
583 _, matches = complete(line_buffer=u"d[u'a\\u05d0")
563 nt.assert_in("u05d0']", matches) # tokenized after \\
584 nt.assert_in("u05d0b", matches) # tokenized after \\
564
585
565 # query using character
586 # query using character
566 _, matches = complete(line_buffer=unicode_type("d[u'a\xd7\x90", 'utf8'))
587 _, matches = complete(line_buffer=u"d[u'a\u05d0")
567 nt.assert_in("']", matches)
588 nt.assert_in(u"a\u05d0b", matches)
589
590 with greedy_completion():
591 _, matches = complete(line_buffer="d[")
592 nt.assert_in("d[u'abc']", matches)
593 nt.assert_in("d[u'a\\u05d0b']", matches)
594
595 _, matches = complete(line_buffer="d['a")
596 nt.assert_in("d['abc']", matches)
597 nt.assert_not_in("d[u'a\\u05d0b']", matches)
598
599 _, matches = complete(line_buffer="d[u'a")
600 nt.assert_in("d[u'abc']", matches)
601 nt.assert_in("d[u'a\\u05d0b']", matches)
602
603 _, matches = complete(line_buffer="d[U'a")
604 nt.assert_in("d[U'abc']", matches)
605 nt.assert_in("d[U'a\\u05d0b']", matches)
606
607 # query using escape
608 _, matches = complete(line_buffer=u"d[u'a\\u05d0")
609 nt.assert_in("d[u'a\\u05d0b']", matches) # tokenized after \\
610
611 # query using character
612 _, matches = complete(line_buffer=u"d[u'a\u05d0")
613 nt.assert_in(u"d[u'a\u05d0b']", matches)
568
614
569
615
570 @dec.onlyif(sys.version_info[0] >= 3, 'This test only applies in Py>=3')
616 @dec.onlyif(sys.version_info[0] >= 3, 'This test only applies in Py>=3')
@@ -573,15 +619,25 def test_dict_key_completion_unicode_py3():
573 ip = get_ipython()
619 ip = get_ipython()
574 complete = ip.Completer.complete
620 complete = ip.Completer.complete
575
621
576 ip.user_ns['d'] = {unicode_type(b'a\xd7\x90', 'utf8'): None}
622 ip.user_ns['d'] = {u'a\u05d0': None}
577
623
578 # query using escape
624 # query using escape
579 _, matches = complete(line_buffer="d['a\\u05d0")
625 _, matches = complete(line_buffer="d['a\\u05d0")
580 nt.assert_in("u05d0']", matches) # tokenized after \\
626 nt.assert_in("u05d0", matches) # tokenized after \\
581
627
582 # query using character
628 # query using character
583 _, matches = complete(line_buffer=unicode_type(b"d['a\xd7\x90", 'utf8'))
629 _, matches = complete(line_buffer="d['a\u05d0")
584 nt.assert_in(unicode_type(b"a\xd7\x90']", 'utf8'), matches)
630 nt.assert_in(u"a\u05d0", matches)
631
632 with greedy_completion():
633 # query using escape
634 _, matches = complete(line_buffer="d['a\\u05d0")
635 nt.assert_in("d['a\\u05d0']", matches) # tokenized after \\
636
637 # query using character
638 _, matches = complete(line_buffer="d['a\u05d0")
639 nt.assert_in(u"d['a\u05d0']", matches)
640
585
641
586
642
587 @dec.skip_without('numpy')
643 @dec.skip_without('numpy')
@@ -592,8 +648,8 def test_struct_array_key_completion():
592 complete = ip.Completer.complete
648 complete = ip.Completer.complete
593 ip.user_ns['d'] = numpy.array([], dtype=[('hello', 'f'), ('world', 'f')])
649 ip.user_ns['d'] = numpy.array([], dtype=[('hello', 'f'), ('world', 'f')])
594 _, matches = complete(line_buffer="d['")
650 _, matches = complete(line_buffer="d['")
595 nt.assert_in("hello']", matches)
651 nt.assert_in("hello", matches)
596 nt.assert_in("world']", matches)
652 nt.assert_in("world", matches)
597
653
598
654
599 @dec.skip_without('pandas')
655 @dec.skip_without('pandas')
@@ -604,8 +660,8 def test_dataframe_key_completion():
604 complete = ip.Completer.complete
660 complete = ip.Completer.complete
605 ip.user_ns['d'] = pandas.DataFrame({'hello': [1], 'world': [2]})
661 ip.user_ns['d'] = pandas.DataFrame({'hello': [1], 'world': [2]})
606 _, matches = complete(line_buffer="d['")
662 _, matches = complete(line_buffer="d['")
607 nt.assert_in("hello']", matches)
663 nt.assert_in("hello", matches)
608 nt.assert_in("world']", matches)
664 nt.assert_in("world", matches)
609
665
610
666
611 def test_dict_key_completion_invalids():
667 def test_dict_key_completion_invalids():
General Comments 0
You need to be logged in to leave comments. Login now