1"""Terminal input and output prompts."""
2
3from pygments.token import Token
4import sys
5
6from IPython.core.displayhook import DisplayHook
7
8from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens
9from prompt_toolkit.shortcuts import print_formatted_text
10from prompt_toolkit.enums import EditingMode
11
12
13class Prompts:
14 def __init__(self, shell):
15 self.shell = shell
16
17 def vi_mode(self):
18 if (getattr(self.shell.pt_app, 'editing_mode', None) == EditingMode.VI
19 and self.shell.prompt_includes_vi_mode):
20 mode = str(self.shell.pt_app.app.vi_state.input_mode)
21 if mode.startswith('InputMode.'):
22 mode = mode[10:13].lower()
23 elif mode.startswith('vi-'):
24 mode = mode[3:6]
25 return '['+mode+'] '
26 return ''
27
28 def current_line(self) -> int:
29 if self.shell.pt_app is not None:
30 return self.shell.pt_app.default_buffer.document.cursor_position_row or 0
31 return 0
32
33 def in_prompt_tokens(self):
34 return [
35 (Token.Prompt.Mode, self.vi_mode()),
36 (
37 Token.Prompt.LineNumber,
38 self.shell.prompt_line_number_format.format(
39 line=1, rel_line=-self.current_line()
40 ),
41 ),
42 (Token.Prompt, "In ["),
43 (Token.PromptNum, str(self.shell.execution_count)),
44 (Token.Prompt, ']: '),
45 ]
46
47 def _width(self):
48 return fragment_list_width(self.in_prompt_tokens())
49
50 def continuation_prompt_tokens(
51 self,
52 width: int | None = None,
53 *,
54 lineno: int | None = None,
55 wrap_count: int | None = None,
56 ):
57 if width is None:
58 width = self._width()
59 line = lineno + 1 if lineno is not None else 0
60 if wrap_count:
61 return [
62 (
63 Token.Prompt.Wrap,
64 # (" " * (width - 2)) + "\N{HORIZONTAL ELLIPSIS} ",
65 (" " * (width - 2)) + "\N{VERTICAL ELLIPSIS} ",
66 ),
67 ]
68 prefix = " " * len(
69 self.vi_mode()
70 ) + self.shell.prompt_line_number_format.format(
71 line=line, rel_line=line - self.current_line() - 1
72 )
73 return [
74 (
75 getattr(Token.Prompt.Continuation, f"L{lineno}"),
76 prefix + (" " * (width - len(prefix) - 5)) + "...:",
77 ),
78 (Token.Prompt.Padding, " "),
79 ]
80
81 def rewrite_prompt_tokens(self):
82 width = self._width()
83 return [
84 (Token.Prompt, ('-' * (width - 2)) + '> '),
85 ]
86
87 def out_prompt_tokens(self):
88 return [
89 (Token.OutPrompt, 'Out['),
90 (Token.OutPromptNum, str(self.shell.execution_count)),
91 (Token.OutPrompt, ']: '),
92 ]
93
94class ClassicPrompts(Prompts):
95 def in_prompt_tokens(self):
96 return [
97 (Token.Prompt, '>>> '),
98 ]
99
100 def continuation_prompt_tokens(self, width=None):
101 return [(Token.Prompt.Continuation, "... ")]
102
103 def rewrite_prompt_tokens(self):
104 return []
105
106 def out_prompt_tokens(self):
107 return []
108
109class RichPromptDisplayHook(DisplayHook):
110 """Subclass of base display hook using coloured prompt"""
111 def write_output_prompt(self):
112 sys.stdout.write(self.shell.separate_out)
113 # If we're not displaying a prompt, it effectively ends with a newline,
114 # because the output will be left-aligned.
115 self.prompt_end_newline = True
116
117 if self.do_full_cache:
118 tokens = self.shell.prompts.out_prompt_tokens()
119 prompt_txt = "".join(s for _, s in tokens)
120 if prompt_txt and not prompt_txt.endswith("\n"):
121 # Ask for a newline before multiline output
122 self.prompt_end_newline = False
123
124 if self.shell.pt_app:
125 print_formatted_text(PygmentsTokens(tokens),
126 style=self.shell.pt_app.app.style, end='',
127 )
128 else:
129 sys.stdout.write(prompt_txt)
130
131 def write_format_data(self, format_dict, md_dict=None) -> None:
132 assert self.shell is not None
133 if self.shell.mime_renderers:
134
135 for mime, handler in self.shell.mime_renderers.items():
136 if mime in format_dict:
137 handler(format_dict[mime], None)
138 return
139
140 super().write_format_data(format_dict, md_dict)
141