|
from __future__ import annotations |
|
|
|
import os |
|
from typing import Callable, Iterable |
|
|
|
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: Callable[[], list[str]] | None = None, |
|
file_filter: Callable[[str], bool] | None = 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 |
|
|
|
|
|
|
|
|
|
if len(text) < self.min_input_len: |
|
return |
|
|
|
try: |
|
|
|
if self.expanduser: |
|
text = os.path.expanduser(text) |
|
|
|
|
|
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() |
|
|
|
|
|
prefix = os.path.basename(text) |
|
|
|
|
|
filenames = [] |
|
for directory in directories: |
|
|
|
if os.path.isdir(directory): |
|
for filename in os.listdir(directory): |
|
if filename.startswith(prefix): |
|
filenames.append((directory, filename)) |
|
|
|
|
|
filenames = sorted(filenames, key=lambda k: k[1]) |
|
|
|
|
|
for directory, filename in filenames: |
|
completion = filename[len(prefix) :] |
|
full_name = os.path.join(directory, filename) |
|
|
|
if os.path.isdir(full_name): |
|
|
|
|
|
|
|
filename += "/" |
|
elif self.only_directories: |
|
continue |
|
|
|
if not self.file_filter(full_name): |
|
continue |
|
|
|
yield Completion( |
|
text=completion, |
|
start_position=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, |
|
) |
|
|