Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/completion/nested.py: 33%

39 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1""" 

2Nestedcompleter for completion of hierarchical data structures. 

3""" 

4from __future__ import annotations 

5 

6from typing import Any, Iterable, Mapping, Set, Union 

7 

8from prompt_toolkit.completion import CompleteEvent, Completer, Completion 

9from prompt_toolkit.completion.word_completer import WordCompleter 

10from prompt_toolkit.document import Document 

11 

12__all__ = ["NestedCompleter"] 

13 

14# NestedDict = Mapping[str, Union['NestedDict', Set[str], None, Completer]] 

15NestedDict = Mapping[str, Union[Any, Set[str], None, Completer]] 

16 

17 

18class NestedCompleter(Completer): 

19 """ 

20 Completer which wraps around several other completers, and calls any the 

21 one that corresponds with the first word of the input. 

22 

23 By combining multiple `NestedCompleter` instances, we can achieve multiple 

24 hierarchical levels of autocompletion. This is useful when `WordCompleter` 

25 is not sufficient. 

26 

27 If you need multiple levels, check out the `from_nested_dict` classmethod. 

28 """ 

29 

30 def __init__( 

31 self, options: dict[str, Completer | None], ignore_case: bool = True 

32 ) -> None: 

33 self.options = options 

34 self.ignore_case = ignore_case 

35 

36 def __repr__(self) -> str: 

37 return f"NestedCompleter({self.options!r}, ignore_case={self.ignore_case!r})" 

38 

39 @classmethod 

40 def from_nested_dict(cls, data: NestedDict) -> NestedCompleter: 

41 """ 

42 Create a `NestedCompleter`, starting from a nested dictionary data 

43 structure, like this: 

44 

45 .. code:: 

46 

47 data = { 

48 'show': { 

49 'version': None, 

50 'interfaces': None, 

51 'clock': None, 

52 'ip': {'interface': {'brief'}} 

53 }, 

54 'exit': None 

55 'enable': None 

56 } 

57 

58 The value should be `None` if there is no further completion at some 

59 point. If all values in the dictionary are None, it is also possible to 

60 use a set instead. 

61 

62 Values in this data structure can be a completers as well. 

63 """ 

64 options: dict[str, Completer | None] = {} 

65 for key, value in data.items(): 

66 if isinstance(value, Completer): 

67 options[key] = value 

68 elif isinstance(value, dict): 

69 options[key] = cls.from_nested_dict(value) 

70 elif isinstance(value, set): 

71 options[key] = cls.from_nested_dict({item: None for item in value}) 

72 else: 

73 assert value is None 

74 options[key] = None 

75 

76 return cls(options) 

77 

78 def get_completions( 

79 self, document: Document, complete_event: CompleteEvent 

80 ) -> Iterable[Completion]: 

81 # Split document. 

82 text = document.text_before_cursor.lstrip() 

83 stripped_len = len(document.text_before_cursor) - len(text) 

84 

85 # If there is a space, check for the first term, and use a 

86 # subcompleter. 

87 if " " in text: 

88 first_term = text.split()[0] 

89 completer = self.options.get(first_term) 

90 

91 # If we have a sub completer, use this for the completions. 

92 if completer is not None: 

93 remaining_text = text[len(first_term) :].lstrip() 

94 move_cursor = len(text) - len(remaining_text) + stripped_len 

95 

96 new_document = Document( 

97 remaining_text, 

98 cursor_position=document.cursor_position - move_cursor, 

99 ) 

100 

101 yield from completer.get_completions(new_document, complete_event) 

102 

103 # No space in the input: behave exactly like `WordCompleter`. 

104 else: 

105 completer = WordCompleter( 

106 list(self.options.keys()), ignore_case=self.ignore_case 

107 ) 

108 yield from completer.get_completions(document, complete_event)