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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

41 statements  

1""" 

2Nestedcompleter for completion of hierarchical data structures. 

3""" 

4 

5from __future__ import annotations 

6 

7from collections.abc import Iterable, Mapping 

8from typing import Any 

9 

10from prompt_toolkit.completion import CompleteEvent, Completer, Completion 

11from prompt_toolkit.completion.word_completer import WordCompleter 

12from prompt_toolkit.document import Document 

13 

14__all__ = ["NestedCompleter"] 

15 

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

17NestedDict = Mapping[str, Any | set[str] | None | Completer] 

18 

19 

20class NestedCompleter(Completer): 

21 """ 

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

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

24 

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

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

27 is not sufficient. 

28 

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

30 """ 

31 

32 def __init__( 

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

34 ) -> None: 

35 self.options = options 

36 self.ignore_case = ignore_case 

37 

38 def __repr__(self) -> str: 

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

40 

41 @classmethod 

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

43 """ 

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

45 structure, like this: 

46 

47 .. code:: 

48 

49 data = { 

50 'show': { 

51 'version': None, 

52 'interfaces': None, 

53 'clock': None, 

54 'ip': {'interface': {'brief'}} 

55 }, 

56 'exit': None 

57 'enable': None 

58 } 

59 

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

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

62 use a set instead. 

63 

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

65 """ 

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

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

68 if isinstance(value, Completer): 

69 options[key] = value 

70 elif isinstance(value, dict): 

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

72 elif isinstance(value, set): 

73 options[key] = cls.from_nested_dict(dict.fromkeys(value)) 

74 else: 

75 assert value is None 

76 options[key] = None 

77 

78 return cls(options) 

79 

80 def get_completions( 

81 self, document: Document, complete_event: CompleteEvent 

82 ) -> Iterable[Completion]: 

83 # Split document. 

84 text = document.text_before_cursor.lstrip() 

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

86 

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

88 # subcompleter. 

89 if " " in text: 

90 first_term = text.split()[0] 

91 completer = self.options.get(first_term) 

92 

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

94 if completer is not None: 

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

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

97 

98 new_document = Document( 

99 remaining_text, 

100 cursor_position=document.cursor_position - move_cursor, 

101 ) 

102 

103 yield from completer.get_completions(new_document, complete_event) 

104 

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

106 else: 

107 completer = WordCompleter( 

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

109 ) 

110 yield from completer.get_completions(document, complete_event)