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