File size: 3,178 Bytes
5ef3875
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""User input: prompt_toolkit session with history and multiline editing."""

from __future__ import annotations

import sys
from pathlib import Path
from typing import Iterable

PROMPT = "ycode> "


class InputReader:
    """Reads one request at a time.

    * Enter submits; Esc+Enter (or Alt+Enter) inserts a newline.
    * A line ending in a backslash continues on the next line.
    * Up/Down walk through persistent history (~/.ycode/history).

    Falls back to plain ``input()`` when stdin is not a terminal.
    """

    def __init__(self, history_path: Path | None = None, commands: Iterable[str] = ()) -> None:
        self._session = None
        if sys.stdin.isatty() and sys.stdout.isatty():
            try:
                self._session = self._build_session(history_path, list(commands))
            except Exception:  # noqa: BLE001 - unusual terminals: degrade to input()
                self._session = None

    @staticmethod
    def _build_session(history_path: Path | None, commands: list[str]):
        from prompt_toolkit import PromptSession
        from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
        from prompt_toolkit.completion import WordCompleter
        from prompt_toolkit.history import FileHistory, InMemoryHistory
        from prompt_toolkit.key_binding import KeyBindings

        bindings = KeyBindings()

        @bindings.add("escape", "enter")
        def _newline(event) -> None:  # noqa: ANN001
            event.current_buffer.insert_text("\n")

        @bindings.add("enter")
        def _submit(event) -> None:  # noqa: ANN001
            buffer = event.current_buffer
            if buffer.text.endswith("\\"):
                buffer.delete_before_cursor(1)
                buffer.insert_text("\n")
            else:
                buffer.validate_and_handle()

        history = InMemoryHistory()
        if history_path is not None:
            try:
                history_path.parent.mkdir(parents=True, exist_ok=True)
                history = FileHistory(str(history_path))
            except OSError:
                pass
        completer = WordCompleter(commands, sentence=True) if commands else None
        return PromptSession(
            history=history,
            auto_suggest=AutoSuggestFromHistory(),
            completer=completer,
            complete_while_typing=True,
            reserve_space_for_menu=4,
            key_bindings=bindings,
            multiline=True,
            prompt_continuation=lambda width, line_number, is_soft_wrap: "." * (width - 1) + " ",
        )

    def read(self) -> str:
        """Return the next request. Raises EOFError (Ctrl+D) / KeyboardInterrupt (Ctrl+C)."""
        if self._session is not None:
            from prompt_toolkit.formatted_text import HTML

            return self._session.prompt(HTML("<ansicyan><b>ycode&gt;</b></ansicyan> "))
        lines = []
        prompt = PROMPT
        while True:
            line = input(prompt)
            if line.endswith("\\"):
                lines.append(line[:-1])
                prompt = "...    "
                continue
            lines.append(line)
            return "\n".join(lines)