• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#   Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
2#                       Armin Rigo
3#
4#                        All Rights Reserved
5#
6#
7# Permission to use, copy, modify, and distribute this software and
8# its documentation for any purpose is hereby granted without fee,
9# provided that the above copyright notice appear in all copies and
10# that both that copyright notice and this permission notice appear in
11# supporting documentation.
12#
13# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
14# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
16# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
17# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
18# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
19# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20
21"""This is an alternative to python_reader which tries to emulate
22the CPython prompt as closely as possible, with the exception of
23allowing multiline input and multiline history entries.
24"""
25
26from __future__ import annotations
27
28import _sitebuiltins
29import linecache
30import functools
31import os
32import sys
33import code
34
35from .readline import _get_reader, multiline_input
36
37TYPE_CHECKING = False
38
39if TYPE_CHECKING:
40    from typing import Any
41
42
43_error: tuple[type[Exception], ...] | type[Exception]
44try:
45    from .unix_console import _error
46except ModuleNotFoundError:
47    from .windows_console import _error
48
49def check() -> str:
50    """Returns the error message if there is a problem initializing the state."""
51    try:
52        _get_reader()
53    except _error as e:
54        if term := os.environ.get("TERM", ""):
55            term = f"; TERM={term}"
56        return str(str(e) or repr(e) or "unknown error") + term
57    return ""
58
59
60def _strip_final_indent(text: str) -> str:
61    # kill spaces and tabs at the end, but only if they follow '\n'.
62    # meant to remove the auto-indentation only (although it would of
63    # course also remove explicitly-added indentation).
64    short = text.rstrip(" \t")
65    n = len(short)
66    if n > 0 and text[n - 1] == "\n":
67        return short
68    return text
69
70
71def _clear_screen():
72    reader = _get_reader()
73    reader.scheduled_commands.append("clear_screen")
74
75
76REPL_COMMANDS = {
77    "exit": _sitebuiltins.Quitter('exit', ''),
78    "quit": _sitebuiltins.Quitter('quit' ,''),
79    "copyright": _sitebuiltins._Printer('copyright', sys.copyright),
80    "help": "help",
81    "clear": _clear_screen,
82    "\x1a": _sitebuiltins.Quitter('\x1a', ''),
83}
84
85
86def _more_lines(console: code.InteractiveConsole, unicodetext: str) -> bool:
87    # ooh, look at the hack:
88    src = _strip_final_indent(unicodetext)
89    try:
90        code = console.compile(src, "<stdin>", "single")
91    except (OverflowError, SyntaxError, ValueError):
92        lines = src.splitlines(keepends=True)
93        if len(lines) == 1:
94            return False
95
96        last_line = lines[-1]
97        was_indented = last_line.startswith((" ", "\t"))
98        not_empty = last_line.strip() != ""
99        incomplete = not last_line.endswith("\n")
100        return (was_indented or not_empty) and incomplete
101    else:
102        return code is None
103
104
105def run_multiline_interactive_console(
106    console: code.InteractiveConsole,
107    *,
108    future_flags: int = 0,
109) -> None:
110    from .readline import _setup
111    _setup(console.locals)
112    if future_flags:
113        console.compile.compiler.flags |= future_flags
114
115    more_lines = functools.partial(_more_lines, console)
116    input_n = 0
117
118    def maybe_run_command(statement: str) -> bool:
119        statement = statement.strip()
120        if statement in console.locals or statement not in REPL_COMMANDS:
121            return False
122
123        reader = _get_reader()
124        reader.history.pop()  # skip internal commands in history
125        command = REPL_COMMANDS[statement]
126        if callable(command):
127            command()
128            return True
129
130        if isinstance(command, str):
131            # Internal readline commands require a prepared reader like
132            # inside multiline_input.
133            reader.prepare()
134            reader.refresh()
135            reader.do_cmd((command, [statement]))
136            reader.restore()
137            return True
138
139        return False
140
141    while 1:
142        try:
143            try:
144                sys.stdout.flush()
145            except Exception:
146                pass
147
148            ps1 = getattr(sys, "ps1", ">>> ")
149            ps2 = getattr(sys, "ps2", "... ")
150            try:
151                statement = multiline_input(more_lines, ps1, ps2)
152            except EOFError:
153                break
154
155            if maybe_run_command(statement):
156                continue
157
158            input_name = f"<python-input-{input_n}>"
159            linecache._register_code(input_name, statement, "<stdin>")  # type: ignore[attr-defined]
160            more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single")  # type: ignore[call-arg]
161            assert not more
162            input_n += 1
163        except KeyboardInterrupt:
164            r = _get_reader()
165            if r.input_trans is r.isearch_trans:
166                r.do_cmd(("isearch-end", [""]))
167            r.pos = len(r.get_unicode())
168            r.dirty = True
169            r.refresh()
170            r.in_bracketed_paste = False
171            console.write("\nKeyboardInterrupt\n")
172            console.resetbuffer()
173        except MemoryError:
174            console.write("\nMemoryError\n")
175            console.resetbuffer()
176