Skip to content

Add nested autocompletion that supports meta #1396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env python
"""
Example of nested autocompletion with meta.
"""
from prompt_toolkit import prompt
from prompt_toolkit.completion.nested_meta import NestedMetaCompleter, NestedMetaData

completer = NestedMetaCompleter(
[
NestedMetaData(
"show",
"some show help",
[
NestedMetaCompleter(
[NestedMetaData("version", "show version meta", [])]
),
NestedMetaCompleter(
[NestedMetaData("clock", "[beta] show clock meta", [])]
),
NestedMetaCompleter(
[
NestedMetaData(
"ip",
"show ip meta",
[
NestedMetaCompleter(
[
NestedMetaData(
"interfaces",
"interfaces meta",
[
NestedMetaCompleter(
[
NestedMetaData(
"brief", "brief meta", []
)
]
),
],
)
]
),
],
)
]
),
],
),
NestedMetaData("exit", "now", []),
]
)


def main():
text = prompt("Type a command: ", completer=completer)
print("You said: %s" % text)


if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions prompt_toolkit/completion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .filesystem import ExecutableCompleter, PathCompleter
from .fuzzy_completer import FuzzyCompleter, FuzzyWordCompleter
from .nested import NestedCompleter
from .nested_meta import NestedMetaCompleter, NestedMetaData
from .word_completer import WordCompleter

__all__ = [
Expand All @@ -34,6 +35,8 @@
"FuzzyWordCompleter",
# Nested.
"NestedCompleter",
"NestedMetaCompleter",
"NestedMetaData",
# Word completer.
"WordCompleter",
# Deduplicate
Expand Down
68 changes: 68 additions & 0 deletions prompt_toolkit/completion/nested_meta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python
"""
Nestedcompleter for completion of hierarchical data structures, with meta.
"""
from dataclasses import dataclass
from typing import Iterable

from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.completion.word_completer import WordCompleter
from prompt_toolkit.document import Document
from prompt_toolkit.formatted_text import AnyFormattedText

__all__ = ["NestedMetaData", "NestedMetaCompleter"]


@dataclass
class NestedMetaData:
key: str
meta: AnyFormattedText
data: Iterable["NestedMetaCompleter"]


class NestedMetaCompleter(Completer):
def __init__(
self, data: Iterable["NestedMetaData"], ignore_case: bool = True
) -> None:

self.data = data
self.ignore_case = ignore_case

def __repr__(self) -> str:
return "NestedMetaCompleter(%r, ignore_case=%r)" % (self.data, self.ignore_case)

def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
# If there is a space, check for the first term, and use a
# subcompleter.
text = document.text_before_cursor.lstrip()
if " " in text:
# Split document.
first_term = text.split()[0]
_completers = [item.data for item in self.data if item.key == first_term]
if not _completers:
return
# If we have a sub completer, use this for the completions.
completers = _completers[0]
remaining_text = text[len(first_term) :].lstrip()
stripped_len = len(document.text_before_cursor) - len(text)
move_cursor = len(text) - len(remaining_text) + stripped_len

new_document = Document(
remaining_text,
cursor_position=document.cursor_position - move_cursor,
)
for completer in completers:
for c in completer.get_completions(new_document, complete_event):
yield c

# No space in the input: behave exactly like `WordCompleter`.
else:
w_completer = WordCompleter(
[item.key for item in self.data],
ignore_case=False,
meta_dict={item.key: item.meta for item in self.data},
)
for c in w_completer.get_completions(document, complete_event):
yield c
81 changes: 81 additions & 0 deletions tests/test_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
DeduplicateCompleter,
FuzzyWordCompleter,
NestedCompleter,
NestedMetaCompleter,
NestedMetaData,
PathCompleter,
WordCompleter,
merge_completers,
Expand Down Expand Up @@ -443,6 +445,85 @@ def test_nested_completer():
assert {c.text for c in completions} == {"brief"}


def test_nested_completer_meta():
completer = NestedMetaCompleter(
[
NestedMetaData(
"show",
"some show help",
[
NestedMetaCompleter(
[NestedMetaData("version", "show version meta", [])]
),
NestedMetaCompleter(
[NestedMetaData("clock", "[beta] show clock meta", [])]
),
NestedMetaCompleter(
[
NestedMetaData(
"ip",
"show ip meta",
[
NestedMetaCompleter(
[
NestedMetaData(
"interfaces",
"interfaces meta",
[
NestedMetaCompleter(
[
NestedMetaData(
"brief",
"brief meta",
[],
)
]
),
],
)
]
),
],
)
]
),
],
),
NestedMetaData("exit", "now", []),
]
)

# Empty input.
completions = completer.get_completions(Document(""), CompleteEvent())
assert {c.text for c in completions} == {"show", "exit"}

# One character.
completions = completer.get_completions(Document("s"), CompleteEvent())
assert {c.text for c in completions} == {"show"}

# One word.
completions = completer.get_completions(Document("show"), CompleteEvent())
assert {c.text for c in completions} == {"show"}

# One word + space.
completions = completer.get_completions(Document("show "), CompleteEvent())
assert {c.text for c in completions} == {"version", "clock", "ip"}

# One word + space + one character.
completions = completer.get_completions(Document("show i"), CompleteEvent())
assert {c.text for c in completions} == {"ip"}

# One space + one word + space + one character.
completions = completer.get_completions(Document(" show i"), CompleteEvent())
assert {c.text for c in completions} == {"ip"}

# Test nested set.
completions = completer.get_completions(
Document("show ip interfaces br"), CompleteEvent()
)
assert {c.text for c in completions} == {"brief"}


def test_deduplicate_completer():
def create_completer(deduplicate: bool):
return merge_completers(
Expand Down