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