##// END OF EJS Templates
Merge pull request #12395 from cphyc/feature/tuple-completion
Matthias Bussonnier -
r25854:fdd32759 merge
parent child Browse files
Show More
@@ -753,7 +753,8 b' def get__all__entries(obj):'
753 return [w for w in words if isinstance(w, str)]
753 return [w for w in words if isinstance(w, str)]
754
754
755
755
756 def match_dict_keys(keys: List[Union[str, bytes]], prefix: str, delims: str) -> Tuple[str, int, List[str]]:
756 def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str,
757 extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]:
757 """Used by dict_key_matches, matching the prefix to a list of keys
758 """Used by dict_key_matches, matching the prefix to a list of keys
758
759
759 Parameters
760 Parameters
@@ -761,9 +762,12 b' def match_dict_keys(keys: List[Union[str, bytes]], prefix: str, delims: str) -> '
761 keys:
762 keys:
762 list of keys in dictionary currently being completed.
763 list of keys in dictionary currently being completed.
763 prefix:
764 prefix:
764 Part of the text already typed by the user. e.g. `mydict[b'fo`
765 Part of the text already typed by the user. E.g. `mydict[b'fo`
765 delims:
766 delims:
766 String of delimiters to consider when finding the current key.
767 String of delimiters to consider when finding the current key.
768 extra_prefix: optional
769 Part of the text already typed in multi-key index cases. E.g. for
770 `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`.
767
771
768 Returns
772 Returns
769 =======
773 =======
@@ -774,10 +778,37 b' def match_dict_keys(keys: List[Union[str, bytes]], prefix: str, delims: str) -> '
774 ``matches`` a list of replacement/completion
778 ``matches`` a list of replacement/completion
775
779
776 """
780 """
777 keys = [k for k in keys if isinstance(k, (str, bytes))]
781 prefix_tuple = extra_prefix if extra_prefix else ()
782 Nprefix = len(prefix_tuple)
783 def filter_prefix_tuple(key):
784 # Reject too short keys
785 if len(key) <= Nprefix:
786 return False
787 # Reject keys with non str/bytes in it
788 for k in key:
789 if not isinstance(k, (str, bytes)):
790 return False
791 # Reject keys that do not match the prefix
792 for k, pt in zip(key, prefix_tuple):
793 if k != pt:
794 return False
795 # All checks passed!
796 return True
797
798 filtered_keys:List[Union[str,bytes]] = []
799 def _add_to_filtered_keys(key):
800 if isinstance(key, (str, bytes)):
801 filtered_keys.append(key)
802
803 for k in keys:
804 if isinstance(k, tuple):
805 if filter_prefix_tuple(k):
806 _add_to_filtered_keys(k[Nprefix])
807 else:
808 _add_to_filtered_keys(k)
809
778 if not prefix:
810 if not prefix:
779 return '', 0, [repr(k) for k in keys
811 return '', 0, [repr(k) for k in filtered_keys]
780 if isinstance(k, (str, bytes))]
781 quote_match = re.search('["\']', prefix)
812 quote_match = re.search('["\']', prefix)
782 assert quote_match is not None # silence mypy
813 assert quote_match is not None # silence mypy
783 quote = quote_match.group()
814 quote = quote_match.group()
@@ -793,7 +824,7 b' def match_dict_keys(keys: List[Union[str, bytes]], prefix: str, delims: str) -> '
793 token_prefix = token_match.group()
824 token_prefix = token_match.group()
794
825
795 matched:List[str] = []
826 matched:List[str] = []
796 for key in keys:
827 for key in filtered_keys:
797 try:
828 try:
798 if not key.startswith(prefix_str):
829 if not key.startswith(prefix_str):
799 continue
830 continue
@@ -1641,6 +1672,15 b' class IPCompleter(Completer):'
1641 )
1672 )
1642 \[ # open bracket
1673 \[ # open bracket
1643 \s* # and optional whitespace
1674 \s* # and optional whitespace
1675 # Capture any number of str-like objects (e.g. "a", "b", 'c')
1676 ((?:[uUbB]? # string prefix (r not handled)
1677 (?:
1678 '(?:[^']|(?<!\\)\\')*'
1679 |
1680 "(?:[^"]|(?<!\\)\\")*"
1681 )
1682 \s*,\s*
1683 )*)
1644 ([uUbB]? # string prefix (r not handled)
1684 ([uUbB]? # string prefix (r not handled)
1645 (?: # unclosed string
1685 (?: # unclosed string
1646 '(?:[^']|(?<!\\)\\')*
1686 '(?:[^']|(?<!\\)\\')*
@@ -1662,10 +1702,11 b' class IPCompleter(Completer):'
1662 }
1702 }
1663
1703
1664 match = regexps[self.greedy].search(self.text_until_cursor)
1704 match = regexps[self.greedy].search(self.text_until_cursor)
1705
1665 if match is None:
1706 if match is None:
1666 return []
1707 return []
1667
1708
1668 expr, prefix = match.groups()
1709 expr, prefix0, prefix = match.groups()
1669 try:
1710 try:
1670 obj = eval(expr, self.namespace)
1711 obj = eval(expr, self.namespace)
1671 except Exception:
1712 except Exception:
@@ -1677,7 +1718,10 b' class IPCompleter(Completer):'
1677 keys = self._get_keys(obj)
1718 keys = self._get_keys(obj)
1678 if not keys:
1719 if not keys:
1679 return keys
1720 return keys
1680 closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims)
1721
1722 extra_prefix = eval(prefix0) if prefix0 != '' else None
1723
1724 closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims, extra_prefix=extra_prefix)
1681 if not matches:
1725 if not matches:
1682 return matches
1726 return matches
1683
1727
@@ -1687,7 +1731,7 b' class IPCompleter(Completer):'
1687 # - the start of the completion
1731 # - the start of the completion
1688 text_start = len(self.text_until_cursor) - len(text)
1732 text_start = len(self.text_until_cursor) - len(text)
1689 if prefix:
1733 if prefix:
1690 key_start = match.start(2)
1734 key_start = match.start(3)
1691 completion_start = key_start + token_offset
1735 completion_start = key_start + token_offset
1692 else:
1736 else:
1693 key_start = completion_start = match.end()
1737 key_start = completion_start = match.end()
@@ -2089,7 +2133,7 b' class IPCompleter(Completer):'
2089
2133
2090 # Start with a clean slate of completions
2134 # Start with a clean slate of completions
2091 matches = []
2135 matches = []
2092
2136
2093 # FIXME: we should extend our api to return a dict with completions for
2137 # FIXME: we should extend our api to return a dict with completions for
2094 # different types of objects. The rlcomplete() method could then
2138 # different types of objects. The rlcomplete() method could then
2095 # simply collapse the dict into a list for readline, but we'd have
2139 # simply collapse the dict into a list for readline, but we'd have
@@ -846,6 +846,35 b' class TestCompleter(unittest.TestCase):'
846
846
847 match_dict_keys
847 match_dict_keys
848
848
849 def test_match_dict_keys_tuple(self):
850 """
851 Test that match_dict_keys called with extra prefix works on a couple of use case,
852 does return what expected, and does not crash.
853 """
854 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
855
856 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
857
858 # Completion on first key == "foo"
859 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
860 assert match_dict_keys(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
861 assert match_dict_keys(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
862 assert match_dict_keys(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
863 assert match_dict_keys(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
864 assert match_dict_keys(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
865 assert match_dict_keys(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
866 assert match_dict_keys(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
867
868 # No Completion
869 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
870 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
871
872 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
873 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2", "foo2"])
874 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
875 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
876 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
877
849 def test_dict_key_completion_string(self):
878 def test_dict_key_completion_string(self):
850 """Test dictionary key completion for string keys"""
879 """Test dictionary key completion for string keys"""
851 ip = get_ipython()
880 ip = get_ipython()
@@ -891,12 +920,16 b' class TestCompleter(unittest.TestCase):'
891 "bad": None,
920 "bad": None,
892 object(): None,
921 object(): None,
893 5: None,
922 5: None,
923 ("abe", None): None,
924 (None, "abf"): None
894 }
925 }
895
926
896 _, matches = complete(line_buffer="d['a")
927 _, matches = complete(line_buffer="d['a")
897 nt.assert_in("abc", matches)
928 nt.assert_in("abc", matches)
898 nt.assert_in("abd", matches)
929 nt.assert_in("abd", matches)
899 nt.assert_not_in("bad", matches)
930 nt.assert_not_in("bad", matches)
931 nt.assert_not_in("abe", matches)
932 nt.assert_not_in("abf", matches)
900 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
933 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
901
934
902 # check escaping and whitespace
935 # check escaping and whitespace
@@ -930,6 +963,74 b' class TestCompleter(unittest.TestCase):'
930 _, matches = complete(line_buffer="d['before-af")
963 _, matches = complete(line_buffer="d['before-af")
931 nt.assert_in("before-after", matches)
964 nt.assert_in("before-after", matches)
932
965
966 # check completion on tuple-of-string keys at different stage - on first key
967 ip.user_ns["d"] = {('foo', 'bar'): None}
968 _, matches = complete(line_buffer="d[")
969 nt.assert_in("'foo'", matches)
970 nt.assert_not_in("'foo']", matches)
971 nt.assert_not_in("'bar'", matches)
972 nt.assert_not_in("foo", matches)
973 nt.assert_not_in("bar", matches)
974
975 # - match the prefix
976 _, matches = complete(line_buffer="d['f")
977 nt.assert_in("foo", matches)
978 nt.assert_not_in("foo']", matches)
979 nt.assert_not_in("foo\"]", matches)
980 _, matches = complete(line_buffer="d['foo")
981 nt.assert_in("foo", matches)
982
983 # - can complete on second key
984 _, matches = complete(line_buffer="d['foo', ")
985 nt.assert_in("'bar'", matches)
986 _, matches = complete(line_buffer="d['foo', 'b")
987 nt.assert_in("bar", matches)
988 nt.assert_not_in("foo", matches)
989
990 # - does not propose missing keys
991 _, matches = complete(line_buffer="d['foo', 'f")
992 nt.assert_not_in("bar", matches)
993 nt.assert_not_in("foo", matches)
994
995 # check sensitivity to following context
996 _, matches = complete(line_buffer="d['foo',]", cursor_pos=8)
997 nt.assert_in("'bar'", matches)
998 nt.assert_not_in("bar", matches)
999 nt.assert_not_in("'foo'", matches)
1000 nt.assert_not_in("foo", matches)
1001
1002 _, matches = complete(line_buffer="d['']", cursor_pos=3)
1003 nt.assert_in("foo", matches)
1004 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1005
1006 _, matches = complete(line_buffer='d[""]', cursor_pos=3)
1007 nt.assert_in("foo", matches)
1008 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1009
1010 _, matches = complete(line_buffer='d["foo","]', cursor_pos=9)
1011 nt.assert_in("bar", matches)
1012 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1013
1014 _, matches = complete(line_buffer='d["foo",]', cursor_pos=8)
1015 nt.assert_in("'bar'", matches)
1016 nt.assert_not_in("bar", matches)
1017
1018 # Can complete with longer tuple keys
1019 ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None}
1020
1021 # - can complete second key
1022 _, matches = complete(line_buffer="d['foo', 'b")
1023 nt.assert_in('bar', matches)
1024 nt.assert_not_in('foo', matches)
1025 nt.assert_not_in('foobar', matches)
1026
1027 # - can complete third key
1028 _, matches = complete(line_buffer="d['foo', 'bar', 'fo")
1029 nt.assert_in('foobar', matches)
1030 nt.assert_not_in('foo', matches)
1031 nt.assert_not_in('bar', matches)
1032
1033
933 def test_dict_key_completion_contexts(self):
1034 def test_dict_key_completion_contexts(self):
934 """Test expression contexts in which dict key completion occurs"""
1035 """Test expression contexts in which dict key completion occurs"""
935 ip = get_ipython()
1036 ip = get_ipython()
General Comments 0
You need to be logged in to leave comments. Login now