Show More
@@ -79,6 +79,7 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 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_ |
|
|
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 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 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 |
|
|
|
929 | suf = closing_quote or '' | |
|
930 | else: | |
|
931 |
suf = |
|
|
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 | |||
|
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 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 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 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 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' |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 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 |
|
|
435 |
nt.assert_in("abd |
|
|
436 |
nt.assert_not_in("bad |
|
|
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 |
|
|
442 |
nt.assert_in("a\\'b |
|
|
443 |
nt.assert_in("a\"b |
|
|
444 |
nt.assert_in("a word |
|
|
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 |
|
|
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 |
|
|
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\" |
|
|
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 |
|
|
473 | nt.assert_in("b", matches) | |
|
461 | 474 | |
|
462 | 475 | |
|
463 | 476 | def test_dict_key_completion_contexts(): |
@@ -482,10 +495,10 def test_dict_key_completion_contexts(): | |||
|
482 | 495 | |
|
483 | 496 | def assert_completion(**kwargs): |
|
484 | 497 | _, matches = complete(**kwargs) |
|
485 |
nt.assert_in("'abc' |
|
|
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 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 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' |
|
|
516 |
nt.assert_in("b'abd' |
|
|
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' |
|
|
521 |
nt.assert_not_in("b'abc' |
|
|
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 |
|
|
525 |
nt.assert_not_in("abc |
|
|
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 |
|
|
529 |
nt.assert_not_in("abc |
|
|
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 |
|
|
533 |
nt.assert_not_in("abd |
|
|
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 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 |
u |
|
|
564 | u'a\u05d0b': None} | |
|
544 | 565 | |
|
545 | 566 | _, matches = complete(line_buffer="d[") |
|
546 |
nt.assert_in("u'abc' |
|
|
547 |
nt.assert_in("u'a\\u05d0' |
|
|
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 |
|
|
551 |
nt.assert_not_in("a\\u05d0 |
|
|
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 |
|
|
555 |
nt.assert_in("a\\u05d0 |
|
|
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 |
|
|
559 |
nt.assert_in("a\\u05d0 |
|
|
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 |
|
|
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=u |
|
|
567 |
nt.assert_in(" |
|
|
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 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'] = {u |
|
|
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 |
|
|
626 | nt.assert_in("u05d0", matches) # tokenized after \\ | |
|
581 | 627 | |
|
582 | 628 | # query using character |
|
583 |
_, matches = complete(line_buffer= |
|
|
584 |
nt.assert_in(u |
|
|
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 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 |
|
|
596 |
nt.assert_in("world |
|
|
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 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 |
|
|
608 |
nt.assert_in("world |
|
|
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