95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
|
"""
|
||
|
Completer for a regular grammar.
|
||
|
"""
|
||
|
from typing import Dict, Iterable, List
|
||
|
|
||
|
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
|
||
|
from prompt_toolkit.document import Document
|
||
|
|
||
|
from .compiler import Match, _CompiledGrammar
|
||
|
|
||
|
__all__ = [
|
||
|
"GrammarCompleter",
|
||
|
]
|
||
|
|
||
|
|
||
|
class GrammarCompleter(Completer):
|
||
|
"""
|
||
|
Completer which can be used for autocompletion according to variables in
|
||
|
the grammar. Each variable can have a different autocompleter.
|
||
|
|
||
|
:param compiled_grammar: `GrammarCompleter` instance.
|
||
|
:param completers: `dict` mapping variable names of the grammar to the
|
||
|
`Completer` instances to be used for each variable.
|
||
|
"""
|
||
|
|
||
|
def __init__(
|
||
|
self, compiled_grammar: _CompiledGrammar, completers: Dict[str, Completer]
|
||
|
) -> None:
|
||
|
|
||
|
self.compiled_grammar = compiled_grammar
|
||
|
self.completers = completers
|
||
|
|
||
|
def get_completions(
|
||
|
self, document: Document, complete_event: CompleteEvent
|
||
|
) -> Iterable[Completion]:
|
||
|
m = self.compiled_grammar.match_prefix(document.text_before_cursor)
|
||
|
|
||
|
if m:
|
||
|
completions = self._remove_duplicates(
|
||
|
self._get_completions_for_match(m, complete_event)
|
||
|
)
|
||
|
|
||
|
for c in completions:
|
||
|
yield c
|
||
|
|
||
|
def _get_completions_for_match(
|
||
|
self, match: Match, complete_event: CompleteEvent
|
||
|
) -> Iterable[Completion]:
|
||
|
"""
|
||
|
Yield all the possible completions for this input string.
|
||
|
(The completer assumes that the cursor position was at the end of the
|
||
|
input string.)
|
||
|
"""
|
||
|
for match_variable in match.end_nodes():
|
||
|
varname = match_variable.varname
|
||
|
start = match_variable.start
|
||
|
|
||
|
completer = self.completers.get(varname)
|
||
|
|
||
|
if completer:
|
||
|
text = match_variable.value
|
||
|
|
||
|
# Unwrap text.
|
||
|
unwrapped_text = self.compiled_grammar.unescape(varname, text)
|
||
|
|
||
|
# Create a document, for the completions API (text/cursor_position)
|
||
|
document = Document(unwrapped_text, len(unwrapped_text))
|
||
|
|
||
|
# Call completer
|
||
|
for completion in completer.get_completions(document, complete_event):
|
||
|
new_text = (
|
||
|
unwrapped_text[: len(text) + completion.start_position]
|
||
|
+ completion.text
|
||
|
)
|
||
|
|
||
|
# Wrap again.
|
||
|
yield Completion(
|
||
|
text=self.compiled_grammar.escape(varname, new_text),
|
||
|
start_position=start - len(match.string),
|
||
|
display=completion.display,
|
||
|
display_meta=completion.display_meta,
|
||
|
)
|
||
|
|
||
|
def _remove_duplicates(self, items: Iterable[Completion]) -> List[Completion]:
|
||
|
"""
|
||
|
Remove duplicates, while keeping the order.
|
||
|
(Sometimes we have duplicates, because the there several matches of the
|
||
|
same grammar, each yielding similar completions.)
|
||
|
"""
|
||
|
result: List[Completion] = []
|
||
|
for i in items:
|
||
|
if i not in result:
|
||
|
result.append(i)
|
||
|
return result
|