Coverage for src/importnb/completer.py: 0%

1# coding: utf-8

2"""# Fuzzy completion

3

4 The fuzzy importer could be confusing and perhaps a completer could help.

5

6

7 >>> ip = __import__("IPython").get_ipython(); load_ipython_extension(ip)

8 >>> assert ip.complete('importnb.__pleter', 'import importnb.__pleter')[1]

9 >>> assert ip.complete('__find__', 'import __find__')[1]

10 >>> assert ip.complete('IPy', '\timport IPy')[1]

11 >>> assert ip.complete('_______________plet__', 'from importnb import _______________plet__')[1]

12"""

13

14 import string

15 from fnmatch import fnmatch

16 from pathlib import Path

17

18 from .finder import fuzzy_file_search

19

20"""To provide the most reliable fuzzy imports `fuzzify_string` replaces the imported with one that complies with the fuzzy finder.

21"""

22

23

24 def fuzzify_string(str):

25 return (str[0] in string.ascii_letters + "_" and str[0] or "_") + "".join(

26 letter if letter in string.ascii_letters + "_" + string.digits else "_"

27 for letter in str[1:]

28 )

29

30

31"""`align_match` replaces the the begining of the match with a prefix that matches that completer query name.

32"""

33

34

35 def align_match(match, prefix, *, i=0):

36 pattern = prefix.replace("__", "*").replace("_", "?").strip()

37 for i in range(len(match)):

38 if fnmatch(match[:i], pattern):

39 break

40 else:

41 i += 1

42 return prefix + match[i:]

43

44

45"""* `predict_fuzzy` will take a fully qualified fuzzy name completions. This is the main function for the completer.

46"""

47

48

49 def predict_fuzzy(fullname):

50 package, paths, specs, extras = "", [], [], []

51 if "." in fullname:

52 package, fullname = fullname.rsplit(".", 1)

53 fullname = fullname.strip()

54 try:

55 module = __import__("importlib").import_module(package)

56 paths.append(Path(module.__file__).parent)

57 extras = [object for object in dir(module) if object.startswith("fullname")]

58 except:

59 ...

60 else:

61 paths = map(Path, __import__("sys").path)

62 query = fullname

63 while not query.endswith("__"):

64 query += "_"

65 for path in paths:

66 specs.extend(

67 str(object.relative_to(path).with_suffix(""))

68 for object in fuzzy_file_search(path, query)

69 )

70

71 return set(

72 (package and package + "." or "") + align_match(fuzzify_string(spec), fullname)

73 for spec in specs

74 ).union(set(extras))

75

76

77 def fuzzy_complete_event(self, event):

78 event.line = event.line.lstrip()

79 symbol = event.symbol

80 if event.line.startswith("from"):

81 package = event.line.split(" import ", 1)[0].lstrip().lstrip("from").lstrip()

82 if " import" in event.line:

83 symbol = (package + "." + symbol).lstrip(".")

84 return [object.lstrip(package).lstrip(".") for object in predict_fuzzy(symbol)]

85

86 return predict_fuzzy(symbol)

87

88

89"""* The extension adds the new fuzzy completer. Our completer has a higher priority than the default completers. Since we stripped the leading whitespace from the completion line event; the extension will permit completion on tabbed lines.

90"""

91

92

93 def load_ipython_extension(ip):

94 ip.set_hook("complete_command", fuzzy_complete_event, str_key="aimport", priority=25)

95 ip.set_hook("complete_command", fuzzy_complete_event, str_key="import", priority=25)

96 ip.set_hook("complete_command", fuzzy_complete_event, str_key="%reload_ext", priority=25)

97 ip.set_hook("complete_command", fuzzy_complete_event, str_key="%load_ext", priority=25)

98 ip.set_hook("complete_command", fuzzy_complete_event, str_key="from", priority=25)