| | 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, |
| | ) |
| |
|