Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
36
venv/Lib/site-packages/prompt_toolkit/completion/__init__.py
Normal file
36
venv/Lib/site-packages/prompt_toolkit/completion/__init__.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from .base import (
|
||||
CompleteEvent,
|
||||
Completer,
|
||||
Completion,
|
||||
DummyCompleter,
|
||||
DynamicCompleter,
|
||||
ThreadedCompleter,
|
||||
get_common_complete_suffix,
|
||||
merge_completers,
|
||||
)
|
||||
from .filesystem import ExecutableCompleter, PathCompleter
|
||||
from .fuzzy_completer import FuzzyCompleter, FuzzyWordCompleter
|
||||
from .nested import NestedCompleter
|
||||
from .word_completer import WordCompleter
|
||||
|
||||
__all__ = [
|
||||
# Base.
|
||||
"Completion",
|
||||
"Completer",
|
||||
"ThreadedCompleter",
|
||||
"DummyCompleter",
|
||||
"DynamicCompleter",
|
||||
"CompleteEvent",
|
||||
"merge_completers",
|
||||
"get_common_complete_suffix",
|
||||
# Filesystem.
|
||||
"PathCompleter",
|
||||
"ExecutableCompleter",
|
||||
# Fuzzy
|
||||
"FuzzyCompleter",
|
||||
"FuzzyWordCompleter",
|
||||
# Nested.
|
||||
"NestedCompleter",
|
||||
# Word completer.
|
||||
"WordCompleter",
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
349
venv/Lib/site-packages/prompt_toolkit/completion/base.py
Normal file
349
venv/Lib/site-packages/prompt_toolkit/completion/base.py
Normal file
|
@ -0,0 +1,349 @@
|
|||
"""
|
||||
"""
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from typing import AsyncGenerator, Callable, Iterable, Optional, Sequence
|
||||
|
||||
from prompt_toolkit.document import Document
|
||||
from prompt_toolkit.eventloop import generator_to_async_generator
|
||||
from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples
|
||||
|
||||
__all__ = [
|
||||
"Completion",
|
||||
"Completer",
|
||||
"ThreadedCompleter",
|
||||
"DummyCompleter",
|
||||
"DynamicCompleter",
|
||||
"CompleteEvent",
|
||||
"merge_completers",
|
||||
"get_common_complete_suffix",
|
||||
]
|
||||
|
||||
|
||||
class Completion:
|
||||
"""
|
||||
:param text: The new string that will be inserted into the document.
|
||||
:param start_position: Position relative to the cursor_position where the
|
||||
new text will start. The text will be inserted between the
|
||||
start_position and the original cursor position.
|
||||
:param display: (optional string or formatted text) If the completion has
|
||||
to be displayed differently in the completion menu.
|
||||
:param display_meta: (Optional string or formatted text) Meta information
|
||||
about the completion, e.g. the path or source where it's coming from.
|
||||
This can also be a callable that returns a string.
|
||||
:param style: Style string.
|
||||
:param selected_style: Style string, used for a selected completion.
|
||||
This can override the `style` parameter.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: str,
|
||||
start_position: int = 0,
|
||||
display: Optional[AnyFormattedText] = None,
|
||||
display_meta: Optional[AnyFormattedText] = None,
|
||||
style: str = "",
|
||||
selected_style: str = "",
|
||||
) -> None:
|
||||
|
||||
from prompt_toolkit.formatted_text import to_formatted_text
|
||||
|
||||
self.text = text
|
||||
self.start_position = start_position
|
||||
self._display_meta = display_meta
|
||||
|
||||
if display is None:
|
||||
display = text
|
||||
|
||||
self.display = to_formatted_text(display)
|
||||
|
||||
self.style = style
|
||||
self.selected_style = selected_style
|
||||
|
||||
assert self.start_position <= 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if isinstance(self.display, str) and self.display == self.text:
|
||||
return "%s(text=%r, start_position=%r)" % (
|
||||
self.__class__.__name__,
|
||||
self.text,
|
||||
self.start_position,
|
||||
)
|
||||
else:
|
||||
return "%s(text=%r, start_position=%r, display=%r)" % (
|
||||
self.__class__.__name__,
|
||||
self.text,
|
||||
self.start_position,
|
||||
self.display,
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Completion):
|
||||
return False
|
||||
return (
|
||||
self.text == other.text
|
||||
and self.start_position == other.start_position
|
||||
and self.display == other.display
|
||||
and self._display_meta == other._display_meta
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.text, self.start_position, self.display, self._display_meta))
|
||||
|
||||
@property
|
||||
def display_text(self) -> str:
|
||||
" The 'display' field as plain text. "
|
||||
from prompt_toolkit.formatted_text import fragment_list_to_text
|
||||
|
||||
return fragment_list_to_text(self.display)
|
||||
|
||||
@property
|
||||
def display_meta(self) -> StyleAndTextTuples:
|
||||
" Return meta-text. (This is lazy when using a callable). "
|
||||
from prompt_toolkit.formatted_text import to_formatted_text
|
||||
|
||||
return to_formatted_text(self._display_meta or "")
|
||||
|
||||
@property
|
||||
def display_meta_text(self) -> str:
|
||||
" The 'meta' field as plain text. "
|
||||
from prompt_toolkit.formatted_text import fragment_list_to_text
|
||||
|
||||
return fragment_list_to_text(self.display_meta)
|
||||
|
||||
def new_completion_from_position(self, position: int) -> "Completion":
|
||||
"""
|
||||
(Only for internal use!)
|
||||
Get a new completion by splitting this one. Used by `Application` when
|
||||
it needs to have a list of new completions after inserting the common
|
||||
prefix.
|
||||
"""
|
||||
assert position - self.start_position >= 0
|
||||
|
||||
return Completion(
|
||||
text=self.text[position - self.start_position :],
|
||||
display=self.display,
|
||||
display_meta=self._display_meta,
|
||||
)
|
||||
|
||||
|
||||
class CompleteEvent:
|
||||
"""
|
||||
Event that called the completer.
|
||||
|
||||
:param text_inserted: When True, it means that completions are requested
|
||||
because of a text insert. (`Buffer.complete_while_typing`.)
|
||||
:param completion_requested: When True, it means that the user explicitly
|
||||
pressed the `Tab` key in order to view the completions.
|
||||
|
||||
These two flags can be used for instance to implement a completer that
|
||||
shows some completions when ``Tab`` has been pressed, but not
|
||||
automatically when the user presses a space. (Because of
|
||||
`complete_while_typing`.)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, text_inserted: bool = False, completion_requested: bool = False
|
||||
) -> None:
|
||||
assert not (text_inserted and completion_requested)
|
||||
|
||||
#: Automatic completion while typing.
|
||||
self.text_inserted = text_inserted
|
||||
|
||||
#: Used explicitly requested completion by pressing 'tab'.
|
||||
self.completion_requested = completion_requested
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "%s(text_inserted=%r, completion_requested=%r)" % (
|
||||
self.__class__.__name__,
|
||||
self.text_inserted,
|
||||
self.completion_requested,
|
||||
)
|
||||
|
||||
|
||||
class Completer(metaclass=ABCMeta):
|
||||
"""
|
||||
Base class for completer implementations.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
"""
|
||||
This should be a generator that yields :class:`.Completion` instances.
|
||||
|
||||
If the generation of completions is something expensive (that takes a
|
||||
lot of time), consider wrapping this `Completer` class in a
|
||||
`ThreadedCompleter`. In that case, the completer algorithm runs in a
|
||||
background thread and completions will be displayed as soon as they
|
||||
arrive.
|
||||
|
||||
:param document: :class:`~prompt_toolkit.document.Document` instance.
|
||||
:param complete_event: :class:`.CompleteEvent` instance.
|
||||
"""
|
||||
while False:
|
||||
yield
|
||||
|
||||
async def get_completions_async(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> AsyncGenerator[Completion, None]:
|
||||
"""
|
||||
Asynchronous generator for completions. (Probably, you won't have to
|
||||
override this.)
|
||||
|
||||
Asynchronous generator of :class:`.Completion` objects.
|
||||
"""
|
||||
for item in self.get_completions(document, complete_event):
|
||||
yield item
|
||||
|
||||
|
||||
class ThreadedCompleter(Completer):
|
||||
"""
|
||||
Wrapper that runs the `get_completions` generator in a thread.
|
||||
|
||||
(Use this to prevent the user interface from becoming unresponsive if the
|
||||
generation of completions takes too much time.)
|
||||
|
||||
The completions will be displayed as soon as they are produced. The user
|
||||
can already select a completion, even if not all completions are displayed.
|
||||
"""
|
||||
|
||||
def __init__(self, completer: Completer) -> None:
|
||||
self.completer = completer
|
||||
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
return self.completer.get_completions(document, complete_event)
|
||||
|
||||
async def get_completions_async(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> AsyncGenerator[Completion, None]:
|
||||
"""
|
||||
Asynchronous generator of completions.
|
||||
"""
|
||||
async for completion in generator_to_async_generator(
|
||||
lambda: self.completer.get_completions(document, complete_event)
|
||||
):
|
||||
yield completion
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "ThreadedCompleter(%r)" % (self.completer,)
|
||||
|
||||
|
||||
class DummyCompleter(Completer):
|
||||
"""
|
||||
A completer that doesn't return any completion.
|
||||
"""
|
||||
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
return []
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "DummyCompleter()"
|
||||
|
||||
|
||||
class DynamicCompleter(Completer):
|
||||
"""
|
||||
Completer class that can dynamically returns any Completer.
|
||||
|
||||
:param get_completer: Callable that returns a :class:`.Completer` instance.
|
||||
"""
|
||||
|
||||
def __init__(self, get_completer: Callable[[], Optional[Completer]]) -> None:
|
||||
self.get_completer = get_completer
|
||||
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
completer = self.get_completer() or DummyCompleter()
|
||||
return completer.get_completions(document, complete_event)
|
||||
|
||||
async def get_completions_async(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> AsyncGenerator[Completion, None]:
|
||||
completer = self.get_completer() or DummyCompleter()
|
||||
|
||||
async for completion in completer.get_completions_async(
|
||||
document, complete_event
|
||||
):
|
||||
yield completion
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "DynamicCompleter(%r -> %r)" % (self.get_completer, self.get_completer())
|
||||
|
||||
|
||||
class _MergedCompleter(Completer):
|
||||
"""
|
||||
Combine several completers into one.
|
||||
"""
|
||||
|
||||
def __init__(self, completers: Sequence[Completer]) -> None:
|
||||
self.completers = completers
|
||||
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
# Get all completions from the other completers in a blocking way.
|
||||
for completer in self.completers:
|
||||
for c in completer.get_completions(document, complete_event):
|
||||
yield c
|
||||
|
||||
async def get_completions_async(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> AsyncGenerator[Completion, None]:
|
||||
|
||||
# Get all completions from the other completers in a blocking way.
|
||||
for completer in self.completers:
|
||||
async for item in completer.get_completions_async(document, complete_event):
|
||||
yield item
|
||||
|
||||
|
||||
def merge_completers(completers: Sequence[Completer]) -> _MergedCompleter:
|
||||
"""
|
||||
Combine several completers into one.
|
||||
"""
|
||||
return _MergedCompleter(completers)
|
||||
|
||||
|
||||
def get_common_complete_suffix(
|
||||
document: Document, completions: Sequence[Completion]
|
||||
) -> str:
|
||||
"""
|
||||
Return the common prefix for all completions.
|
||||
"""
|
||||
# Take only completions that don't change the text before the cursor.
|
||||
def doesnt_change_before_cursor(completion: Completion) -> bool:
|
||||
end = completion.text[: -completion.start_position]
|
||||
return document.text_before_cursor.endswith(end)
|
||||
|
||||
completions2 = [c for c in completions if doesnt_change_before_cursor(c)]
|
||||
|
||||
# When there is at least one completion that changes the text before the
|
||||
# cursor, don't return any common part.
|
||||
if len(completions2) != len(completions):
|
||||
return ""
|
||||
|
||||
# Return the common prefix.
|
||||
def get_suffix(completion: Completion) -> str:
|
||||
return completion.text[-completion.start_position :]
|
||||
|
||||
return _commonprefix([get_suffix(c) for c in completions2])
|
||||
|
||||
|
||||
def _commonprefix(strings: Iterable[str]) -> str:
|
||||
# Similar to os.path.commonprefix
|
||||
if not strings:
|
||||
return ""
|
||||
|
||||
else:
|
||||
s1 = min(strings)
|
||||
s2 = max(strings)
|
||||
|
||||
for i, c in enumerate(s1):
|
||||
if c != s2[i]:
|
||||
return s1[:i]
|
||||
|
||||
return s1
|
113
venv/Lib/site-packages/prompt_toolkit/completion/filesystem.py
Normal file
113
venv/Lib/site-packages/prompt_toolkit/completion/filesystem.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
import os
|
||||
from typing import Callable, Iterable, List, Optional
|
||||
|
||||
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
|
||||
from prompt_toolkit.document import Document
|
||||
|
||||
__all__ = [
|
||||
"PathCompleter",
|
||||
"ExecutableCompleter",
|
||||
]
|
||||
|
||||
|
||||
class PathCompleter(Completer):
|
||||
"""
|
||||
Complete for Path variables.
|
||||
|
||||
:param get_paths: Callable which returns a list of directories to look into
|
||||
when the user enters a relative path.
|
||||
:param file_filter: Callable which takes a filename and returns whether
|
||||
this file should show up in the completion. ``None``
|
||||
when no filtering has to be done.
|
||||
:param min_input_len: Don't do autocompletion when the input string is shorter.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
only_directories: bool = False,
|
||||
get_paths: Optional[Callable[[], List[str]]] = None,
|
||||
file_filter: Optional[Callable[[str], bool]] = None,
|
||||
min_input_len: int = 0,
|
||||
expanduser: bool = False,
|
||||
) -> None:
|
||||
|
||||
self.only_directories = only_directories
|
||||
self.get_paths = get_paths or (lambda: ["."])
|
||||
self.file_filter = file_filter or (lambda _: True)
|
||||
self.min_input_len = min_input_len
|
||||
self.expanduser = expanduser
|
||||
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
text = document.text_before_cursor
|
||||
|
||||
# Complete only when we have at least the minimal input length,
|
||||
# otherwise, we can too many results and autocompletion will become too
|
||||
# heavy.
|
||||
if len(text) < self.min_input_len:
|
||||
return
|
||||
|
||||
try:
|
||||
# Do tilde expansion.
|
||||
if self.expanduser:
|
||||
text = os.path.expanduser(text)
|
||||
|
||||
# Directories where to look.
|
||||
dirname = os.path.dirname(text)
|
||||
if dirname:
|
||||
directories = [
|
||||
os.path.dirname(os.path.join(p, text)) for p in self.get_paths()
|
||||
]
|
||||
else:
|
||||
directories = self.get_paths()
|
||||
|
||||
# Start of current file.
|
||||
prefix = os.path.basename(text)
|
||||
|
||||
# Get all filenames.
|
||||
filenames = []
|
||||
for directory in directories:
|
||||
# Look for matches in this directory.
|
||||
if os.path.isdir(directory):
|
||||
for filename in os.listdir(directory):
|
||||
if filename.startswith(prefix):
|
||||
filenames.append((directory, filename))
|
||||
|
||||
# Sort
|
||||
filenames = sorted(filenames, key=lambda k: k[1])
|
||||
|
||||
# Yield them.
|
||||
for directory, filename in filenames:
|
||||
completion = filename[len(prefix) :]
|
||||
full_name = os.path.join(directory, filename)
|
||||
|
||||
if os.path.isdir(full_name):
|
||||
# For directories, add a slash to the filename.
|
||||
# (We don't add them to the `completion`. Users can type it
|
||||
# to trigger the autocompletion themselves.)
|
||||
filename += "/"
|
||||
elif self.only_directories:
|
||||
continue
|
||||
|
||||
if not self.file_filter(full_name):
|
||||
continue
|
||||
|
||||
yield Completion(completion, 0, display=filename)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class ExecutableCompleter(PathCompleter):
|
||||
"""
|
||||
Complete only executable files in the current path.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
only_directories=False,
|
||||
min_input_len=1,
|
||||
get_paths=lambda: os.environ.get("PATH", "").split(os.pathsep),
|
||||
file_filter=lambda name: os.access(name, os.X_OK),
|
||||
expanduser=True,
|
||||
),
|
|
@ -0,0 +1,199 @@
|
|||
import re
|
||||
from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union
|
||||
|
||||
from prompt_toolkit.document import Document
|
||||
from prompt_toolkit.filters import FilterOrBool, to_filter
|
||||
from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples
|
||||
|
||||
from .base import CompleteEvent, Completer, Completion
|
||||
from .word_completer import WordCompleter
|
||||
|
||||
__all__ = [
|
||||
"FuzzyCompleter",
|
||||
"FuzzyWordCompleter",
|
||||
]
|
||||
|
||||
|
||||
class FuzzyCompleter(Completer):
|
||||
"""
|
||||
Fuzzy completion.
|
||||
This wraps any other completer and turns it into a fuzzy completer.
|
||||
|
||||
If the list of words is: ["leopard" , "gorilla", "dinosaur", "cat", "bee"]
|
||||
Then trying to complete "oar" would yield "leopard" and "dinosaur", but not
|
||||
the others, because they match the regular expression 'o.*a.*r'.
|
||||
Similar, in another application "djm" could expand to "django_migrations".
|
||||
|
||||
The results are sorted by relevance, which is defined as the start position
|
||||
and the length of the match.
|
||||
|
||||
Notice that this is not really a tool to work around spelling mistakes,
|
||||
like what would be possible with difflib. The purpose is rather to have a
|
||||
quicker or more intuitive way to filter the given completions, especially
|
||||
when many completions have a common prefix.
|
||||
|
||||
Fuzzy algorithm is based on this post:
|
||||
https://blog.amjith.com/fuzzyfinder-in-10-lines-of-python
|
||||
|
||||
:param completer: A :class:`~.Completer` instance.
|
||||
:param WORD: When True, use WORD characters.
|
||||
:param pattern: Regex pattern which selects the characters before the
|
||||
cursor that are considered for the fuzzy matching.
|
||||
:param enable_fuzzy: (bool or `Filter`) Enabled the fuzzy behavior. For
|
||||
easily turning fuzzyness on or off according to a certain condition.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
completer: Completer,
|
||||
WORD: bool = False,
|
||||
pattern: Optional[str] = None,
|
||||
enable_fuzzy: FilterOrBool = True,
|
||||
):
|
||||
|
||||
assert pattern is None or pattern.startswith("^")
|
||||
|
||||
self.completer = completer
|
||||
self.pattern = pattern
|
||||
self.WORD = WORD
|
||||
self.pattern = pattern
|
||||
self.enable_fuzzy = to_filter(enable_fuzzy)
|
||||
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
if self.enable_fuzzy():
|
||||
return self._get_fuzzy_completions(document, complete_event)
|
||||
else:
|
||||
return self.completer.get_completions(document, complete_event)
|
||||
|
||||
def _get_pattern(self) -> str:
|
||||
if self.pattern:
|
||||
return self.pattern
|
||||
if self.WORD:
|
||||
return r"[^\s]+"
|
||||
return "^[a-zA-Z0-9_]*"
|
||||
|
||||
def _get_fuzzy_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
|
||||
word_before_cursor = document.get_word_before_cursor(
|
||||
pattern=re.compile(self._get_pattern())
|
||||
)
|
||||
|
||||
# Get completions
|
||||
document2 = Document(
|
||||
text=document.text[: document.cursor_position - len(word_before_cursor)],
|
||||
cursor_position=document.cursor_position - len(word_before_cursor),
|
||||
)
|
||||
|
||||
completions = list(self.completer.get_completions(document2, complete_event))
|
||||
|
||||
fuzzy_matches: List[_FuzzyMatch] = []
|
||||
|
||||
pat = ".*?".join(map(re.escape, word_before_cursor))
|
||||
pat = "(?=({0}))".format(pat) # lookahead regex to manage overlapping matches
|
||||
regex = re.compile(pat, re.IGNORECASE)
|
||||
for compl in completions:
|
||||
matches = list(regex.finditer(compl.text))
|
||||
if matches:
|
||||
# Prefer the match, closest to the left, then shortest.
|
||||
best = min(matches, key=lambda m: (m.start(), len(m.group(1))))
|
||||
fuzzy_matches.append(
|
||||
_FuzzyMatch(len(best.group(1)), best.start(), compl)
|
||||
)
|
||||
|
||||
def sort_key(fuzzy_match: "_FuzzyMatch") -> Tuple[int, int]:
|
||||
" Sort by start position, then by the length of the match. "
|
||||
return fuzzy_match.start_pos, fuzzy_match.match_length
|
||||
|
||||
fuzzy_matches = sorted(fuzzy_matches, key=sort_key)
|
||||
|
||||
for match in fuzzy_matches:
|
||||
# Include these completions, but set the correct `display`
|
||||
# attribute and `start_position`.
|
||||
yield Completion(
|
||||
match.completion.text,
|
||||
start_position=match.completion.start_position
|
||||
- len(word_before_cursor),
|
||||
display_meta=match.completion.display_meta,
|
||||
display=self._get_display(match, word_before_cursor),
|
||||
style=match.completion.style,
|
||||
)
|
||||
|
||||
def _get_display(
|
||||
self, fuzzy_match: "_FuzzyMatch", word_before_cursor: str
|
||||
) -> AnyFormattedText:
|
||||
"""
|
||||
Generate formatted text for the display label.
|
||||
"""
|
||||
m = fuzzy_match
|
||||
word = m.completion.text
|
||||
|
||||
if m.match_length == 0:
|
||||
# No highlighting when we have zero length matches (no input text).
|
||||
return word
|
||||
|
||||
result: StyleAndTextTuples = []
|
||||
|
||||
# Text before match.
|
||||
result.append(("class:fuzzymatch.outside", word[: m.start_pos]))
|
||||
|
||||
# The match itself.
|
||||
characters = list(word_before_cursor)
|
||||
|
||||
for c in word[m.start_pos : m.start_pos + m.match_length]:
|
||||
classname = "class:fuzzymatch.inside"
|
||||
if characters and c.lower() == characters[0].lower():
|
||||
classname += ".character"
|
||||
del characters[0]
|
||||
|
||||
result.append((classname, c))
|
||||
|
||||
# Text after match.
|
||||
result.append(
|
||||
("class:fuzzymatch.outside", word[m.start_pos + m.match_length :])
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class FuzzyWordCompleter(Completer):
|
||||
"""
|
||||
Fuzzy completion on a list of words.
|
||||
|
||||
(This is basically a `WordCompleter` wrapped in a `FuzzyCompleter`.)
|
||||
|
||||
:param words: List of words or callable that returns a list of words.
|
||||
:param meta_dict: Optional dict mapping words to their meta-information.
|
||||
:param WORD: When True, use WORD characters.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
words: Union[List[str], Callable[[], List[str]]],
|
||||
meta_dict: Optional[Dict[str, str]] = None,
|
||||
WORD: bool = False,
|
||||
) -> None:
|
||||
|
||||
self.words = words
|
||||
self.meta_dict = meta_dict or {}
|
||||
self.WORD = WORD
|
||||
|
||||
self.word_completer = WordCompleter(
|
||||
words=self.words, WORD=self.WORD, meta_dict=self.meta_dict
|
||||
)
|
||||
|
||||
self.fuzzy_completer = FuzzyCompleter(self.word_completer, WORD=self.WORD)
|
||||
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
return self.fuzzy_completer.get_completions(document, complete_event)
|
||||
|
||||
|
||||
_FuzzyMatch = NamedTuple(
|
||||
"_FuzzyMatch",
|
||||
[("match_length", int), ("start_pos", int), ("completion", Completion)],
|
||||
)
|
109
venv/Lib/site-packages/prompt_toolkit/completion/nested.py
Normal file
109
venv/Lib/site-packages/prompt_toolkit/completion/nested.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
"""
|
||||
Nestedcompleter for completion of hierarchical data structures.
|
||||
"""
|
||||
from typing import Any, Dict, Iterable, Mapping, Optional, 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, Optional[Completer]], ignore_case: bool = True
|
||||
) -> None:
|
||||
|
||||
self.options = options
|
||||
self.ignore_case = ignore_case
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "NestedCompleter(%r, ignore_case=%r)" % (self.options, self.ignore_case)
|
||||
|
||||
@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, Optional[Completer]] = {}
|
||||
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,
|
||||
)
|
||||
|
||||
for c in completer.get_completions(new_document, complete_event):
|
||||
yield c
|
||||
|
||||
# No space in the input: behave exactly like `WordCompleter`.
|
||||
else:
|
||||
completer = WordCompleter(
|
||||
list(self.options.keys()), ignore_case=self.ignore_case
|
||||
)
|
||||
for c in completer.get_completions(document, complete_event):
|
||||
yield c
|
|
@ -0,0 +1,84 @@
|
|||
from typing import Callable, Dict, Iterable, List, Optional, Pattern, Union
|
||||
|
||||
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
|
||||
from prompt_toolkit.document import Document
|
||||
|
||||
__all__ = [
|
||||
"WordCompleter",
|
||||
]
|
||||
|
||||
|
||||
class WordCompleter(Completer):
|
||||
"""
|
||||
Simple autocompletion on a list of words.
|
||||
|
||||
:param words: List of words or callable that returns a list of words.
|
||||
:param ignore_case: If True, case-insensitive completion.
|
||||
:param meta_dict: Optional dict mapping words to their meta-text. (This
|
||||
should map strings to strings or formatted text.)
|
||||
:param WORD: When True, use WORD characters.
|
||||
:param sentence: When True, don't complete by comparing the word before the
|
||||
cursor, but by comparing all the text before the cursor. In this case,
|
||||
the list of words is just a list of strings, where each string can
|
||||
contain spaces. (Can not be used together with the WORD option.)
|
||||
:param match_middle: When True, match not only the start, but also in the
|
||||
middle of the word.
|
||||
:param pattern: Optional compiled regex for finding the word before
|
||||
the cursor to complete. When given, use this regex pattern instead of
|
||||
default one (see document._FIND_WORD_RE)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
words: Union[List[str], Callable[[], List[str]]],
|
||||
ignore_case: bool = False,
|
||||
meta_dict: Optional[Dict[str, str]] = None,
|
||||
WORD: bool = False,
|
||||
sentence: bool = False,
|
||||
match_middle: bool = False,
|
||||
pattern: Optional[Pattern[str]] = None,
|
||||
) -> None:
|
||||
|
||||
assert not (WORD and sentence)
|
||||
|
||||
self.words = words
|
||||
self.ignore_case = ignore_case
|
||||
self.meta_dict = meta_dict or {}
|
||||
self.WORD = WORD
|
||||
self.sentence = sentence
|
||||
self.match_middle = match_middle
|
||||
self.pattern = pattern
|
||||
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
# Get list of words.
|
||||
words = self.words
|
||||
if callable(words):
|
||||
words = words()
|
||||
|
||||
# Get word/text before cursor.
|
||||
if self.sentence:
|
||||
word_before_cursor = document.text_before_cursor
|
||||
else:
|
||||
word_before_cursor = document.get_word_before_cursor(
|
||||
WORD=self.WORD, pattern=self.pattern
|
||||
)
|
||||
|
||||
if self.ignore_case:
|
||||
word_before_cursor = word_before_cursor.lower()
|
||||
|
||||
def word_matches(word: str) -> bool:
|
||||
""" True when the word before the cursor matches. """
|
||||
if self.ignore_case:
|
||||
word = word.lower()
|
||||
|
||||
if self.match_middle:
|
||||
return word_before_cursor in word
|
||||
else:
|
||||
return word.startswith(word_before_cursor)
|
||||
|
||||
for a in words:
|
||||
if word_matches(a):
|
||||
display_meta = self.meta_dict.get(a, "")
|
||||
yield Completion(a, -len(word_before_cursor), display_meta=display_meta)
|
Loading…
Add table
Add a link
Reference in a new issue