123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- """
- Nestedcompleter for completion of hierarchical data structures.
- """
- from __future__ import annotations
- from typing import Any, Iterable, Mapping, Set, Union
- from prompt_toolkit.completion import CompleteEvent, Completer, Completion
- from prompt_toolkit.completion.word_completer import WordCompleter
- from prompt_toolkit.document import Document
- __all__ = ["NestedCompleter"]
- # NestedDict = Mapping[str, Union['NestedDict', Set[str], None, Completer]]
- NestedDict = Mapping[str, Union[Any, Set[str], None, Completer]]
- class NestedCompleter(Completer):
- """
- Completer which wraps around several other completers, and calls any the
- one that corresponds with the first word of the input.
- By combining multiple `NestedCompleter` instances, we can achieve multiple
- hierarchical levels of autocompletion. This is useful when `WordCompleter`
- is not sufficient.
- If you need multiple levels, check out the `from_nested_dict` classmethod.
- """
- def __init__(
- self, options: dict[str, Completer | None], ignore_case: bool = True
- ) -> None:
- self.options = options
- self.ignore_case = ignore_case
- def __repr__(self) -> str:
- return f"NestedCompleter({self.options!r}, ignore_case={self.ignore_case!r})"
- @classmethod
- def from_nested_dict(cls, data: NestedDict) -> NestedCompleter:
- """
- Create a `NestedCompleter`, starting from a nested dictionary data
- structure, like this:
- .. code::
- data = {
- 'show': {
- 'version': None,
- 'interfaces': None,
- 'clock': None,
- 'ip': {'interface': {'brief'}}
- },
- 'exit': None
- 'enable': None
- }
- The value should be `None` if there is no further completion at some
- point. If all values in the dictionary are None, it is also possible to
- use a set instead.
- Values in this data structure can be a completers as well.
- """
- options: dict[str, Completer | None] = {}
- for key, value in data.items():
- if isinstance(value, Completer):
- options[key] = value
- elif isinstance(value, dict):
- options[key] = cls.from_nested_dict(value)
- elif isinstance(value, set):
- options[key] = cls.from_nested_dict({item: None for item in value})
- else:
- assert value is None
- options[key] = None
- return cls(options)
- def get_completions(
- self, document: Document, complete_event: CompleteEvent
- ) -> Iterable[Completion]:
- # Split document.
- text = document.text_before_cursor.lstrip()
- stripped_len = len(document.text_before_cursor) - len(text)
- # If there is a space, check for the first term, and use a
- # subcompleter.
- if " " in text:
- first_term = text.split()[0]
- completer = self.options.get(first_term)
- # If we have a sub completer, use this for the completions.
- if completer is not None:
- remaining_text = text[len(first_term) :].lstrip()
- move_cursor = len(text) - len(remaining_text) + stripped_len
- new_document = Document(
- remaining_text,
- cursor_position=document.cursor_position - move_cursor,
- )
- yield from completer.get_completions(new_document, complete_event)
- # No space in the input: behave exactly like `WordCompleter`.
- else:
- completer = WordCompleter(
- list(self.options.keys()), ignore_case=self.ignore_case
- )
- yield from completer.get_completions(document, complete_event)
|