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
« 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
6from typing import Any, Iterable, Mapping, Set, Union
8from prompt_toolkit.completion import CompleteEvent, Completer, Completion
9from prompt_toolkit.completion.word_completer import WordCompleter
10from prompt_toolkit.document import Document
12__all__ = ["NestedCompleter"]
14# NestedDict = Mapping[str, Union['NestedDict', Set[str], None, Completer]]
15NestedDict = Mapping[str, Union[Any, Set[str], None, Completer]]
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.
23 By combining multiple `NestedCompleter` instances, we can achieve multiple
24 hierarchical levels of autocompletion. This is useful when `WordCompleter`
25 is not sufficient.
27 If you need multiple levels, check out the `from_nested_dict` classmethod.
28 """
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
36 def __repr__(self) -> str:
37 return f"NestedCompleter({self.options!r}, ignore_case={self.ignore_case!r})"
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:
45 .. code::
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 }
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.
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
76 return cls(options)
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)
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)
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
96 new_document = Document(
97 remaining_text,
98 cursor_position=document.cursor_position - move_cursor,
99 )
101 yield from completer.get_completions(new_document, complete_event)
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)