##// END OF EJS Templates
Remove debug print statements
Emilio Graff -
Show More
@@ -1,137 +1,131 b''
1 1 """ Import Qt in a manner suitable for an IPython kernel.
2 2
3 3 This is the import used for the `gui=qt` or `matplotlib=qt` initialization.
4 4
5 5 Import Priority:
6 6
7 7 if Qt has been imported anywhere else:
8 8 use that
9 9
10 10 if matplotlib has been imported and doesn't support v2 (<= 1.0.1):
11 11 use PyQt4 @v1
12 12
13 13 Next, ask QT_API env variable
14 14
15 15 if QT_API not set:
16 16 ask matplotlib what it's using. If Qt4Agg or Qt5Agg, then use the
17 17 version matplotlib is configured with
18 18
19 19 else: (matplotlib said nothing)
20 20 # this is the default path - nobody told us anything
21 21 try in this order:
22 22 PyQt default version, PySide, PyQt5
23 23 else:
24 24 use what QT_API says
25 25
26 26 Note that %gui's implementation will always set a `QT_API`, see
27 27 `IPython.terminal.pt_inputhooks.get_inputhook_name_and_func`
28 28
29 29 """
30 30 # NOTE: This is no longer an external, third-party module, and should be
31 31 # considered part of IPython. For compatibility however, it is being kept in
32 32 # IPython/external.
33 33
34 34 import os
35 35 import sys
36 36
37 37 from IPython.external.qt_loaders import (
38 38 load_qt,
39 39 loaded_api,
40 40 enum_factory,
41 41 # QT6
42 42 QT_API_PYQT6,
43 43 QT_API_PYSIDE6,
44 44 # QT5
45 45 QT_API_PYQT5,
46 46 QT_API_PYSIDE2,
47 47 # QT4
48 48 QT_API_PYQTv1,
49 49 QT_API_PYQT,
50 50 QT_API_PYSIDE,
51 51 # default
52 52 QT_API_PYQT_DEFAULT,
53 53 )
54 54
55 55 _qt_apis = (
56 56 # QT6
57 57 QT_API_PYQT6,
58 58 QT_API_PYSIDE6,
59 59 # QT5
60 60 QT_API_PYQT5,
61 61 QT_API_PYSIDE2,
62 62 # QT4
63 63 QT_API_PYQTv1,
64 64 QT_API_PYQT,
65 65 QT_API_PYSIDE,
66 66 # default
67 67 QT_API_PYQT_DEFAULT,
68 68 )
69 69
70 70
71 71 def matplotlib_options(mpl):
72 72 """Constraints placed on an imported matplotlib."""
73 73 if mpl is None:
74 74 return
75 75 backend = mpl.rcParams.get('backend', None)
76 76 if backend == 'Qt4Agg':
77 77 mpqt = mpl.rcParams.get('backend.qt4', None)
78 78 if mpqt is None:
79 79 return None
80 80 if mpqt.lower() == 'pyside':
81 81 return [QT_API_PYSIDE]
82 82 elif mpqt.lower() == 'pyqt4':
83 83 return [QT_API_PYQT_DEFAULT]
84 84 elif mpqt.lower() == 'pyqt4v2':
85 85 return [QT_API_PYQT]
86 86 raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" %
87 87 mpqt)
88 88 elif backend == 'Qt5Agg':
89 89 mpqt = mpl.rcParams.get('backend.qt5', None)
90 90 if mpqt is None:
91 91 return None
92 92 if mpqt.lower() == 'pyqt5':
93 93 return [QT_API_PYQT5]
94 94 raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" %
95 95 mpqt)
96 96
97 97 def get_options():
98 print(f'`get_options` called with {os.environ.get("QT_API", None)=}')
99 98 """Return a list of acceptable QT APIs, in decreasing order of preference."""
100 99 #already imported Qt somewhere. Use that
101 100 loaded = loaded_api()
102 101 if loaded is not None:
103 print(f"`QtCore` already imported: {loaded=}")
104 102 return [loaded]
105 103
106 104 mpl = sys.modules.get("matplotlib", None)
107 print(f"{mpl=}") # will be None of matplotlib has not yet been imported
108 105
109 106 if mpl is not None and tuple(mpl.__version__.split(".")) < ("1", "0", "2"):
110 107 # 1.0.1 only supports PyQt4 v1
111 108 return [QT_API_PYQT_DEFAULT]
112 109
113 110 qt_api = os.environ.get('QT_API', None)
114 111 if qt_api is None:
115 112 #no ETS variable. Ask mpl, then use default fallback path
116 113 return matplotlib_options(mpl) or [
117 114 QT_API_PYQT_DEFAULT,
118 115 QT_API_PYQT6,
119 116 QT_API_PYSIDE6,
120 117 QT_API_PYQT5,
121 118 QT_API_PYSIDE2,
122 119 QT_API_PYQT,
123 120 QT_API_PYSIDE,
124 121 ]
125 122 elif qt_api not in _qt_apis:
126 123 raise RuntimeError("Invalid Qt API %r, valid values are: %r" %
127 124 (qt_api, ', '.join(_qt_apis)))
128 125 else:
129 print(f"{qt_api=}")
130 126 return [qt_api]
131 127
132 128
133 129 api_opts = get_options()
134 print(f"Importing `IPython.terminal.pt_inputhooks.qt` with {api_opts=}")
135 130 QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)
136 print(f"Loaded Qt with {QT_API=}")
137 131 enum_helper = enum_factory(QT_API, QtCore)
@@ -1,778 +1,776 b''
1 1 """IPython terminal interface using prompt_toolkit"""
2 2
3 3 import asyncio
4 4 import os
5 5 import sys
6 6 from warnings import warn
7 7
8 8 from IPython.core.async_helpers import get_asyncio_loop
9 9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 10 from IPython.utils.py3compat import input
11 11 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
12 12 from IPython.utils.process import abbrev_cwd
13 13 from traitlets import (
14 14 Bool,
15 15 Unicode,
16 16 Dict,
17 17 Integer,
18 18 observe,
19 19 Instance,
20 20 Type,
21 21 default,
22 22 Enum,
23 23 Union,
24 24 Any,
25 25 validate,
26 26 Float,
27 27 )
28 28
29 29 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
30 30 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
31 31 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
32 32 from prompt_toolkit.formatted_text import PygmentsTokens
33 33 from prompt_toolkit.history import History
34 34 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
35 35 from prompt_toolkit.output import ColorDepth
36 36 from prompt_toolkit.patch_stdout import patch_stdout
37 37 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
38 38 from prompt_toolkit.styles import DynamicStyle, merge_styles
39 39 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
40 40 from prompt_toolkit import __version__ as ptk_version
41 41
42 42 from pygments.styles import get_style_by_name
43 43 from pygments.style import Style
44 44 from pygments.token import Token
45 45
46 46 from .debugger import TerminalPdb, Pdb
47 47 from .magics import TerminalMagics
48 48 from .pt_inputhooks import get_inputhook_name_and_func
49 49 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
50 50 from .ptutils import IPythonPTCompleter, IPythonPTLexer
51 51 from .shortcuts import create_ipython_shortcuts
52 52
53 53 PTK3 = ptk_version.startswith('3.')
54 54
55 55
56 56 class _NoStyle(Style): pass
57 57
58 58
59 59
60 60 _style_overrides_light_bg = {
61 61 Token.Prompt: '#ansibrightblue',
62 62 Token.PromptNum: '#ansiblue bold',
63 63 Token.OutPrompt: '#ansibrightred',
64 64 Token.OutPromptNum: '#ansired bold',
65 65 }
66 66
67 67 _style_overrides_linux = {
68 68 Token.Prompt: '#ansibrightgreen',
69 69 Token.PromptNum: '#ansigreen bold',
70 70 Token.OutPrompt: '#ansibrightred',
71 71 Token.OutPromptNum: '#ansired bold',
72 72 }
73 73
74 74 def get_default_editor():
75 75 try:
76 76 return os.environ['EDITOR']
77 77 except KeyError:
78 78 pass
79 79 except UnicodeError:
80 80 warn("$EDITOR environment variable is not pure ASCII. Using platform "
81 81 "default editor.")
82 82
83 83 if os.name == 'posix':
84 84 return 'vi' # the only one guaranteed to be there!
85 85 else:
86 86 return 'notepad' # same in Windows!
87 87
88 88 # conservatively check for tty
89 89 # overridden streams can result in things like:
90 90 # - sys.stdin = None
91 91 # - no isatty method
92 92 for _name in ('stdin', 'stdout', 'stderr'):
93 93 _stream = getattr(sys, _name)
94 94 try:
95 95 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty():
96 96 _is_tty = False
97 97 break
98 98 except ValueError:
99 99 # stream is closed
100 100 _is_tty = False
101 101 break
102 102 else:
103 103 _is_tty = True
104 104
105 105
106 106 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
107 107
108 108 def black_reformat_handler(text_before_cursor):
109 109 """
110 110 We do not need to protect against error,
111 111 this is taken care at a higher level where any reformat error is ignored.
112 112 Indeed we may call reformatting on incomplete code.
113 113 """
114 114 import black
115 115
116 116 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
117 117 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
118 118 formatted_text = formatted_text[:-1]
119 119 return formatted_text
120 120
121 121
122 122 def yapf_reformat_handler(text_before_cursor):
123 123 from yapf.yapflib import file_resources
124 124 from yapf.yapflib import yapf_api
125 125
126 126 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
127 127 formatted_text, was_formatted = yapf_api.FormatCode(
128 128 text_before_cursor, style_config=style_config
129 129 )
130 130 if was_formatted:
131 131 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
132 132 formatted_text = formatted_text[:-1]
133 133 return formatted_text
134 134 else:
135 135 return text_before_cursor
136 136
137 137
138 138 class PtkHistoryAdapter(History):
139 139 """
140 140 Prompt toolkit has it's own way of handling history, Where it assumes it can
141 141 Push/pull from history.
142 142
143 143 """
144 144
145 145 def __init__(self, shell):
146 146 super().__init__()
147 147 self.shell = shell
148 148 self._refresh()
149 149
150 150 def append_string(self, string):
151 151 # we rely on sql for that.
152 152 self._loaded = False
153 153 self._refresh()
154 154
155 155 def _refresh(self):
156 156 if not self._loaded:
157 157 self._loaded_strings = list(self.load_history_strings())
158 158
159 159 def load_history_strings(self):
160 160 last_cell = ""
161 161 res = []
162 162 for __, ___, cell in self.shell.history_manager.get_tail(
163 163 self.shell.history_load_length, include_latest=True
164 164 ):
165 165 # Ignore blank lines and consecutive duplicates
166 166 cell = cell.rstrip()
167 167 if cell and (cell != last_cell):
168 168 res.append(cell)
169 169 last_cell = cell
170 170 yield from res[::-1]
171 171
172 172 def store_string(self, string: str) -> None:
173 173 pass
174 174
175 175 class TerminalInteractiveShell(InteractiveShell):
176 176 mime_renderers = Dict().tag(config=True)
177 177
178 178 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
179 179 'to reserve for the tab completion menu, '
180 180 'search history, ...etc, the height of '
181 181 'these menus will at most this value. '
182 182 'Increase it is you prefer long and skinny '
183 183 'menus, decrease for short and wide.'
184 184 ).tag(config=True)
185 185
186 186 pt_app = None
187 187 debugger_history = None
188 188
189 189 debugger_history_file = Unicode(
190 190 "~/.pdbhistory", help="File in which to store and read history"
191 191 ).tag(config=True)
192 192
193 193 simple_prompt = Bool(_use_simple_prompt,
194 194 help="""Use `raw_input` for the REPL, without completion and prompt colors.
195 195
196 196 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
197 197 IPython own testing machinery, and emacs inferior-shell integration through elpy.
198 198
199 199 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
200 200 environment variable is set, or the current terminal is not a tty."""
201 201 ).tag(config=True)
202 202
203 203 @property
204 204 def debugger_cls(self):
205 205 return Pdb if self.simple_prompt else TerminalPdb
206 206
207 207 confirm_exit = Bool(True,
208 208 help="""
209 209 Set to confirm when you try to exit IPython with an EOF (Control-D
210 210 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
211 211 you can force a direct exit without any confirmation.""",
212 212 ).tag(config=True)
213 213
214 214 editing_mode = Unicode('emacs',
215 215 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
216 216 ).tag(config=True)
217 217
218 218 emacs_bindings_in_vi_insert_mode = Bool(
219 219 True,
220 220 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
221 221 ).tag(config=True)
222 222
223 223 modal_cursor = Bool(
224 224 True,
225 225 help="""
226 226 Cursor shape changes depending on vi mode: beam in vi insert mode,
227 227 block in nav mode, underscore in replace mode.""",
228 228 ).tag(config=True)
229 229
230 230 ttimeoutlen = Float(
231 231 0.01,
232 232 help="""The time in milliseconds that is waited for a key code
233 233 to complete.""",
234 234 ).tag(config=True)
235 235
236 236 timeoutlen = Float(
237 237 0.5,
238 238 help="""The time in milliseconds that is waited for a mapped key
239 239 sequence to complete.""",
240 240 ).tag(config=True)
241 241
242 242 autoformatter = Unicode(
243 243 None,
244 244 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
245 245 allow_none=True
246 246 ).tag(config=True)
247 247
248 248 auto_match = Bool(
249 249 False,
250 250 help="""
251 251 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
252 252 Brackets: (), [], {}
253 253 Quotes: '', \"\"
254 254 """,
255 255 ).tag(config=True)
256 256
257 257 mouse_support = Bool(False,
258 258 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
259 259 ).tag(config=True)
260 260
261 261 # We don't load the list of styles for the help string, because loading
262 262 # Pygments plugins takes time and can cause unexpected errors.
263 263 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
264 264 help="""The name or class of a Pygments style to use for syntax
265 265 highlighting. To see available styles, run `pygmentize -L styles`."""
266 266 ).tag(config=True)
267 267
268 268 @validate('editing_mode')
269 269 def _validate_editing_mode(self, proposal):
270 270 if proposal['value'].lower() == 'vim':
271 271 proposal['value']= 'vi'
272 272 elif proposal['value'].lower() == 'default':
273 273 proposal['value']= 'emacs'
274 274
275 275 if hasattr(EditingMode, proposal['value'].upper()):
276 276 return proposal['value'].lower()
277 277
278 278 return self.editing_mode
279 279
280 280
281 281 @observe('editing_mode')
282 282 def _editing_mode(self, change):
283 283 if self.pt_app:
284 284 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
285 285
286 286 def _set_formatter(self, formatter):
287 287 if formatter is None:
288 288 self.reformat_handler = lambda x:x
289 289 elif formatter == 'black':
290 290 self.reformat_handler = black_reformat_handler
291 291 elif formatter == "yapf":
292 292 self.reformat_handler = yapf_reformat_handler
293 293 else:
294 294 raise ValueError
295 295
296 296 @observe("autoformatter")
297 297 def _autoformatter_changed(self, change):
298 298 formatter = change.new
299 299 self._set_formatter(formatter)
300 300
301 301 @observe('highlighting_style')
302 302 @observe('colors')
303 303 def _highlighting_style_changed(self, change):
304 304 self.refresh_style()
305 305
306 306 def refresh_style(self):
307 307 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
308 308
309 309
310 310 highlighting_style_overrides = Dict(
311 311 help="Override highlighting format for specific tokens"
312 312 ).tag(config=True)
313 313
314 314 true_color = Bool(False,
315 315 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
316 316 If your terminal supports true color, the following command should
317 317 print ``TRUECOLOR`` in orange::
318 318
319 319 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
320 320 """,
321 321 ).tag(config=True)
322 322
323 323 editor = Unicode(get_default_editor(),
324 324 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
325 325 ).tag(config=True)
326 326
327 327 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
328 328
329 329 prompts = Instance(Prompts)
330 330
331 331 @default('prompts')
332 332 def _prompts_default(self):
333 333 return self.prompts_class(self)
334 334
335 335 # @observe('prompts')
336 336 # def _(self, change):
337 337 # self._update_layout()
338 338
339 339 @default('displayhook_class')
340 340 def _displayhook_class_default(self):
341 341 return RichPromptDisplayHook
342 342
343 343 term_title = Bool(True,
344 344 help="Automatically set the terminal title"
345 345 ).tag(config=True)
346 346
347 347 term_title_format = Unicode("IPython: {cwd}",
348 348 help="Customize the terminal title format. This is a python format string. " +
349 349 "Available substitutions are: {cwd}."
350 350 ).tag(config=True)
351 351
352 352 display_completions = Enum(('column', 'multicolumn','readlinelike'),
353 353 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
354 354 "'readlinelike'. These options are for `prompt_toolkit`, see "
355 355 "`prompt_toolkit` documentation for more information."
356 356 ),
357 357 default_value='multicolumn').tag(config=True)
358 358
359 359 highlight_matching_brackets = Bool(True,
360 360 help="Highlight matching brackets.",
361 361 ).tag(config=True)
362 362
363 363 extra_open_editor_shortcuts = Bool(False,
364 364 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
365 365 "This is in addition to the F2 binding, which is always enabled."
366 366 ).tag(config=True)
367 367
368 368 handle_return = Any(None,
369 369 help="Provide an alternative handler to be called when the user presses "
370 370 "Return. This is an advanced option intended for debugging, which "
371 371 "may be changed or removed in later releases."
372 372 ).tag(config=True)
373 373
374 374 enable_history_search = Bool(True,
375 375 help="Allows to enable/disable the prompt toolkit history search"
376 376 ).tag(config=True)
377 377
378 378 autosuggestions_provider = Unicode(
379 379 "AutoSuggestFromHistory",
380 380 help="Specifies from which source automatic suggestions are provided. "
381 381 "Can be set to `'AutoSuggestFromHistory`' or `None` to disable"
382 382 "automatic suggestions. Default is `'AutoSuggestFromHistory`'.",
383 383 allow_none=True,
384 384 ).tag(config=True)
385 385
386 386 def _set_autosuggestions(self, provider):
387 387 if provider is None:
388 388 self.auto_suggest = None
389 389 elif provider == "AutoSuggestFromHistory":
390 390 self.auto_suggest = AutoSuggestFromHistory()
391 391 else:
392 392 raise ValueError("No valid provider.")
393 393 if self.pt_app:
394 394 self.pt_app.auto_suggest = self.auto_suggest
395 395
396 396 @observe("autosuggestions_provider")
397 397 def _autosuggestions_provider_changed(self, change):
398 398 provider = change.new
399 399 self._set_autosuggestions(provider)
400 400
401 401 prompt_includes_vi_mode = Bool(True,
402 402 help="Display the current vi mode (when using vi editing mode)."
403 403 ).tag(config=True)
404 404
405 405 @observe('term_title')
406 406 def init_term_title(self, change=None):
407 407 # Enable or disable the terminal title.
408 408 if self.term_title and _is_tty:
409 409 toggle_set_term_title(True)
410 410 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
411 411 else:
412 412 toggle_set_term_title(False)
413 413
414 414 def restore_term_title(self):
415 415 if self.term_title and _is_tty:
416 416 restore_term_title()
417 417
418 418 def init_display_formatter(self):
419 419 super(TerminalInteractiveShell, self).init_display_formatter()
420 420 # terminal only supports plain text
421 421 self.display_formatter.active_types = ["text/plain"]
422 422
423 423 def init_prompt_toolkit_cli(self):
424 424 if self.simple_prompt:
425 425 # Fall back to plain non-interactive output for tests.
426 426 # This is very limited.
427 427 def prompt():
428 428 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
429 429 lines = [input(prompt_text)]
430 430 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
431 431 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
432 432 lines.append( input(prompt_continuation) )
433 433 return '\n'.join(lines)
434 434 self.prompt_for_code = prompt
435 435 return
436 436
437 437 # Set up keyboard shortcuts
438 438 key_bindings = create_ipython_shortcuts(self)
439 439
440 440
441 441 # Pre-populate history from IPython's history database
442 442 history = PtkHistoryAdapter(self)
443 443
444 444 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
445 445 self.style = DynamicStyle(lambda: self._style)
446 446
447 447 editing_mode = getattr(EditingMode, self.editing_mode.upper())
448 448
449 449 self.pt_loop = asyncio.new_event_loop()
450 450 self.pt_app = PromptSession(
451 451 auto_suggest=self.auto_suggest,
452 452 editing_mode=editing_mode,
453 453 key_bindings=key_bindings,
454 454 history=history,
455 455 completer=IPythonPTCompleter(shell=self),
456 456 enable_history_search=self.enable_history_search,
457 457 style=self.style,
458 458 include_default_pygments_style=False,
459 459 mouse_support=self.mouse_support,
460 460 enable_open_in_editor=self.extra_open_editor_shortcuts,
461 461 color_depth=self.color_depth,
462 462 tempfile_suffix=".py",
463 463 **self._extra_prompt_options(),
464 464 )
465 465
466 466 def _make_style_from_name_or_cls(self, name_or_cls):
467 467 """
468 468 Small wrapper that make an IPython compatible style from a style name
469 469
470 470 We need that to add style for prompt ... etc.
471 471 """
472 472 style_overrides = {}
473 473 if name_or_cls == 'legacy':
474 474 legacy = self.colors.lower()
475 475 if legacy == 'linux':
476 476 style_cls = get_style_by_name('monokai')
477 477 style_overrides = _style_overrides_linux
478 478 elif legacy == 'lightbg':
479 479 style_overrides = _style_overrides_light_bg
480 480 style_cls = get_style_by_name('pastie')
481 481 elif legacy == 'neutral':
482 482 # The default theme needs to be visible on both a dark background
483 483 # and a light background, because we can't tell what the terminal
484 484 # looks like. These tweaks to the default theme help with that.
485 485 style_cls = get_style_by_name('default')
486 486 style_overrides.update({
487 487 Token.Number: '#ansigreen',
488 488 Token.Operator: 'noinherit',
489 489 Token.String: '#ansiyellow',
490 490 Token.Name.Function: '#ansiblue',
491 491 Token.Name.Class: 'bold #ansiblue',
492 492 Token.Name.Namespace: 'bold #ansiblue',
493 493 Token.Name.Variable.Magic: '#ansiblue',
494 494 Token.Prompt: '#ansigreen',
495 495 Token.PromptNum: '#ansibrightgreen bold',
496 496 Token.OutPrompt: '#ansired',
497 497 Token.OutPromptNum: '#ansibrightred bold',
498 498 })
499 499
500 500 # Hack: Due to limited color support on the Windows console
501 501 # the prompt colors will be wrong without this
502 502 if os.name == 'nt':
503 503 style_overrides.update({
504 504 Token.Prompt: '#ansidarkgreen',
505 505 Token.PromptNum: '#ansigreen bold',
506 506 Token.OutPrompt: '#ansidarkred',
507 507 Token.OutPromptNum: '#ansired bold',
508 508 })
509 509 elif legacy =='nocolor':
510 510 style_cls=_NoStyle
511 511 style_overrides = {}
512 512 else :
513 513 raise ValueError('Got unknown colors: ', legacy)
514 514 else :
515 515 if isinstance(name_or_cls, str):
516 516 style_cls = get_style_by_name(name_or_cls)
517 517 else:
518 518 style_cls = name_or_cls
519 519 style_overrides = {
520 520 Token.Prompt: '#ansigreen',
521 521 Token.PromptNum: '#ansibrightgreen bold',
522 522 Token.OutPrompt: '#ansired',
523 523 Token.OutPromptNum: '#ansibrightred bold',
524 524 }
525 525 style_overrides.update(self.highlighting_style_overrides)
526 526 style = merge_styles([
527 527 style_from_pygments_cls(style_cls),
528 528 style_from_pygments_dict(style_overrides),
529 529 ])
530 530
531 531 return style
532 532
533 533 @property
534 534 def pt_complete_style(self):
535 535 return {
536 536 'multicolumn': CompleteStyle.MULTI_COLUMN,
537 537 'column': CompleteStyle.COLUMN,
538 538 'readlinelike': CompleteStyle.READLINE_LIKE,
539 539 }[self.display_completions]
540 540
541 541 @property
542 542 def color_depth(self):
543 543 return (ColorDepth.TRUE_COLOR if self.true_color else None)
544 544
545 545 def _extra_prompt_options(self):
546 546 """
547 547 Return the current layout option for the current Terminal InteractiveShell
548 548 """
549 549 def get_message():
550 550 return PygmentsTokens(self.prompts.in_prompt_tokens())
551 551
552 552 if self.editing_mode == 'emacs':
553 553 # with emacs mode the prompt is (usually) static, so we call only
554 554 # the function once. With VI mode it can toggle between [ins] and
555 555 # [nor] so we can't precompute.
556 556 # here I'm going to favor the default keybinding which almost
557 557 # everybody uses to decrease CPU usage.
558 558 # if we have issues with users with custom Prompts we can see how to
559 559 # work around this.
560 560 get_message = get_message()
561 561
562 562 options = {
563 563 'complete_in_thread': False,
564 564 'lexer':IPythonPTLexer(),
565 565 'reserve_space_for_menu':self.space_for_menu,
566 566 'message': get_message,
567 567 'prompt_continuation': (
568 568 lambda width, lineno, is_soft_wrap:
569 569 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
570 570 'multiline': True,
571 571 'complete_style': self.pt_complete_style,
572 572
573 573 # Highlight matching brackets, but only when this setting is
574 574 # enabled, and only when the DEFAULT_BUFFER has the focus.
575 575 'input_processors': [ConditionalProcessor(
576 576 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
577 577 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
578 578 Condition(lambda: self.highlight_matching_brackets))],
579 579 }
580 580 if not PTK3:
581 581 options['inputhook'] = self.inputhook
582 582
583 583 return options
584 584
585 585 def prompt_for_code(self):
586 586 if self.rl_next_input:
587 587 default = self.rl_next_input
588 588 self.rl_next_input = None
589 589 else:
590 590 default = ''
591 591
592 592 # In order to make sure that asyncio code written in the
593 593 # interactive shell doesn't interfere with the prompt, we run the
594 594 # prompt in a different event loop.
595 595 # If we don't do this, people could spawn coroutine with a
596 596 # while/true inside which will freeze the prompt.
597 597
598 598 policy = asyncio.get_event_loop_policy()
599 599 old_loop = get_asyncio_loop()
600 600
601 601 # FIXME: prompt_toolkit is using the deprecated `asyncio.get_event_loop`
602 602 # to get the current event loop.
603 603 # This will probably be replaced by an attribute or input argument,
604 604 # at which point we can stop calling the soon-to-be-deprecated `set_event_loop` here.
605 605 if old_loop is not self.pt_loop:
606 606 policy.set_event_loop(self.pt_loop)
607 607 try:
608 608 with patch_stdout(raw=True):
609 609 text = self.pt_app.prompt(
610 610 default=default,
611 611 **self._extra_prompt_options())
612 612 finally:
613 613 # Restore the original event loop.
614 614 if old_loop is not None and old_loop is not self.pt_loop:
615 615 policy.set_event_loop(old_loop)
616 616
617 617 return text
618 618
619 619 def enable_win_unicode_console(self):
620 620 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
621 621 # console by default, so WUC shouldn't be needed.
622 622 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
623 623 DeprecationWarning,
624 624 stacklevel=2)
625 625
626 626 def init_io(self):
627 627 if sys.platform not in {'win32', 'cli'}:
628 628 return
629 629
630 630 import colorama
631 631 colorama.init()
632 632
633 633 def init_magics(self):
634 634 super(TerminalInteractiveShell, self).init_magics()
635 635 self.register_magics(TerminalMagics)
636 636
637 637 def init_alias(self):
638 638 # The parent class defines aliases that can be safely used with any
639 639 # frontend.
640 640 super(TerminalInteractiveShell, self).init_alias()
641 641
642 642 # Now define aliases that only make sense on the terminal, because they
643 643 # need direct access to the console in a way that we can't emulate in
644 644 # GUI or web frontend
645 645 if os.name == 'posix':
646 646 for cmd in ('clear', 'more', 'less', 'man'):
647 647 self.alias_manager.soft_define_alias(cmd, cmd)
648 648
649 649
650 650 def __init__(self, *args, **kwargs):
651 651 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
652 652 self._set_autosuggestions(self.autosuggestions_provider)
653 653 self.init_prompt_toolkit_cli()
654 654 self.init_term_title()
655 655 self.keep_running = True
656 656 self._set_formatter(self.autoformatter)
657 657
658 658
659 659 def ask_exit(self):
660 660 self.keep_running = False
661 661
662 662 rl_next_input = None
663 663
664 664 def interact(self):
665 665 self.keep_running = True
666 666 while self.keep_running:
667 667 print(self.separate_in, end='')
668 668
669 669 try:
670 670 code = self.prompt_for_code()
671 671 except EOFError:
672 672 if (not self.confirm_exit) \
673 673 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
674 674 self.ask_exit()
675 675
676 676 else:
677 677 if code:
678 678 self.run_cell(code, store_history=True)
679 679
680 680 def mainloop(self):
681 681 # An extra layer of protection in case someone mashing Ctrl-C breaks
682 682 # out of our internal code.
683 683 while True:
684 684 try:
685 685 self.interact()
686 686 break
687 687 except KeyboardInterrupt as e:
688 688 print("\n%s escaped interact()\n" % type(e).__name__)
689 689 finally:
690 690 # An interrupt during the eventloop will mess up the
691 691 # internal state of the prompt_toolkit library.
692 692 # Stopping the eventloop fixes this, see
693 693 # https://github.com/ipython/ipython/pull/9867
694 694 if hasattr(self, '_eventloop'):
695 695 self._eventloop.stop()
696 696
697 697 self.restore_term_title()
698 698
699 699 # try to call some at-exit operation optimistically as some things can't
700 700 # be done during interpreter shutdown. this is technically inaccurate as
701 701 # this make mainlool not re-callable, but that should be a rare if not
702 702 # in existent use case.
703 703
704 704 self._atexit_once()
705 705
706 706
707 707 _inputhook = None
708 708 def inputhook(self, context):
709 709 if self._inputhook is not None:
710 710 self._inputhook(context)
711 711
712 712 active_eventloop = None
713 713 def enable_gui(self, gui=None):
714 print(f"Someone called `enable_gui` with {gui=}.")
715 714 if self._inputhook is not None and gui is not None:
716 715 raise RuntimeError("Shell already running a gui event loop.")
717 716 if gui and (gui not in {"inline", "webagg"}):
718 717 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
719 718 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
720 719 else:
721 print(f"Disconnecting event loop {self._inputhook=}")
722 720 self.active_eventloop = self._inputhook = None
723 721
724 722 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
725 723 # this inputhook.
726 724 if PTK3:
727 725 import asyncio
728 726 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
729 727
730 728 if gui == 'asyncio':
731 729 # When we integrate the asyncio event loop, run the UI in the
732 730 # same event loop as the rest of the code. don't use an actual
733 731 # input hook. (Asyncio is not made for nesting event loops.)
734 732 self.pt_loop = get_asyncio_loop()
735 733
736 734 elif self._inputhook:
737 735 # If an inputhook was set, create a new asyncio event loop with
738 736 # this inputhook for the prompt.
739 737 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
740 738 else:
741 739 # When there's no inputhook, run the prompt in a separate
742 740 # asyncio event loop.
743 741 self.pt_loop = asyncio.new_event_loop()
744 742
745 743 # Run !system commands directly, not through pipes, so terminal programs
746 744 # work correctly.
747 745 system = InteractiveShell.system_raw
748 746
749 747 def auto_rewrite_input(self, cmd):
750 748 """Overridden from the parent class to use fancy rewriting prompt"""
751 749 if not self.show_rewritten_input:
752 750 return
753 751
754 752 tokens = self.prompts.rewrite_prompt_tokens()
755 753 if self.pt_app:
756 754 print_formatted_text(PygmentsTokens(tokens), end='',
757 755 style=self.pt_app.app.style)
758 756 print(cmd)
759 757 else:
760 758 prompt = ''.join(s for t, s in tokens)
761 759 print(prompt, cmd, sep='')
762 760
763 761 _prompts_before = None
764 762 def switch_doctest_mode(self, mode):
765 763 """Switch prompts to classic for %doctest_mode"""
766 764 if mode:
767 765 self._prompts_before = self.prompts
768 766 self.prompts = ClassicPrompts(self)
769 767 elif self._prompts_before:
770 768 self.prompts = self._prompts_before
771 769 self._prompts_before = None
772 770 # self._update_layout()
773 771
774 772
775 773 InteractiveShellABC.register(TerminalInteractiveShell)
776 774
777 775 if __name__ == '__main__':
778 776 TerminalInteractiveShell.instance().interact()
@@ -1,149 +1,147 b''
1 1 import importlib
2 2 import os
3 3
4 4 aliases = {
5 5 'qt4': 'qt',
6 6 'gtk2': 'gtk',
7 7 }
8 8
9 9 backends = [
10 10 "qt",
11 11 "qt4",
12 12 "qt5",
13 13 "qt6",
14 14 "gtk",
15 15 "gtk2",
16 16 "gtk3",
17 17 "gtk4",
18 18 "tk",
19 19 "wx",
20 20 "pyglet",
21 21 "glut",
22 22 "osx",
23 23 "asyncio",
24 24 ]
25 25
26 26 registered = {}
27 27
28 28 def register(name, inputhook):
29 29 """Register the function *inputhook* as an event loop integration."""
30 30 registered[name] = inputhook
31 31
32 32
33 33 class UnknownBackend(KeyError):
34 34 def __init__(self, name):
35 35 self.name = name
36 36
37 37 def __str__(self):
38 38 return ("No event loop integration for {!r}. "
39 39 "Supported event loops are: {}").format(self.name,
40 40 ', '.join(backends + sorted(registered)))
41 41
42 42
43 43 def set_qt_api(gui):
44 44 """Sets the `QT_API` environment variable if it isn't already set."""
45 45
46 46 qt_api = os.environ.get("QT_API", None)
47 47
48 48 from IPython.external.qt_loaders import (
49 49 QT_API_PYQT,
50 50 QT_API_PYQT5,
51 51 QT_API_PYQT6,
52 52 QT_API_PYSIDE,
53 53 QT_API_PYSIDE2,
54 54 QT_API_PYSIDE6,
55 55 QT_API_PYQTv1,
56 56 loaded_api,
57 57 )
58 58
59 59 loaded = loaded_api()
60 60
61 61 qt_env2gui = {
62 62 QT_API_PYSIDE: 'qt4',
63 63 QT_API_PYQTv1: 'qt4',
64 64 QT_API_PYQT: 'qt4',
65 65 QT_API_PYSIDE2: 'qt5',
66 66 QT_API_PYQT5: 'qt5',
67 67 QT_API_PYSIDE6: 'qt6',
68 68 QT_API_PYQT6: 'qt6',
69 69 }
70 70 if loaded is not None and gui != 'qt':
71 71 if qt_env2gui[loaded] != gui:
72 72 raise ImportError(
73 73 f'Cannot switch Qt versions for this session; must use {qt_env2gui[loaded]}.'
74 74 )
75 75
76 76 if qt_api is not None and gui != 'qt':
77 77 if qt_env2gui[qt_api] != gui:
78 78 print(
79 79 f'Request for "{gui}" will be ignored because `QT_API` '
80 80 f'environment variable is set to "{qt_api}"'
81 81 )
82 82 else:
83 83 # NOTE: 'qt4' is not selectable because it's set as an alias for 'qt'; see `aliases` above.
84 84 if gui == "qt4":
85 85 try:
86 86 import PyQt # noqa
87 87
88 88 os.environ["QT_API"] = "pyqt"
89 89 except ImportError:
90 90 try:
91 91 import PySide # noqa
92 92
93 93 os.environ["QT_API"] = "pyside"
94 94 except ImportError:
95 95 # Neither implementation installed; set it to something so IPython gives an error
96 96 os.environ["QT_API"] = "pyqt"
97 97 elif gui == "qt5":
98 98 try:
99 99 import PyQt5 # noqa
100 100
101 101 os.environ["QT_API"] = "pyqt5"
102 102 except ImportError:
103 103 try:
104 104 import PySide2 # noqa
105 105
106 106 os.environ["QT_API"] = "pyside2"
107 107 except ImportError:
108 108 os.environ["QT_API"] = "pyqt5"
109 109 elif gui == "qt6":
110 110 try:
111 111 import PyQt6 # noqa
112 112
113 113 os.environ["QT_API"] = "pyqt6"
114 114 except ImportError:
115 115 try:
116 116 import PySide6 # noqa
117 117
118 118 os.environ["QT_API"] = "pyside6"
119 119 except ImportError:
120 120 os.environ["QT_API"] = "pyqt6"
121 121 elif gui == "qt":
122 122 # Don't set QT_API; let IPython logic choose the version.
123 123 if "QT_API" in os.environ.keys():
124 124 del os.environ["QT_API"]
125 125 else:
126 126 raise ValueError(
127 127 f'Unrecognized Qt version: {gui}. Should be "qt4", "qt5", "qt6", or "qt".'
128 128 )
129 129
130 130
131 131 def get_inputhook_name_and_func(gui):
132 print(f"`get_inputhook_name_and_func` called with {gui=}")
133 132 if gui in registered:
134 133 return gui, registered[gui]
135 134
136 135 if gui not in backends:
137 136 raise UnknownBackend(gui)
138 137
139 138 if gui in aliases:
140 print("gui has an alias")
141 139 return get_inputhook_name_and_func(aliases[gui])
142 140
143 141 gui_mod = gui
144 142 if gui.startswith("qt"):
145 143 set_qt_api(gui)
146 144 gui_mod = "qt"
147 145
148 146 mod = importlib.import_module("IPython.terminal.pt_inputhooks." + gui_mod)
149 147 return gui, mod.inputhook
@@ -1,96 +1,86 b''
1 1 import sys
2 2 import os
3 3 from IPython.external.qt_for_kernel import QtCore, QtGui, enum_helper
4 4 from IPython import get_ipython
5 5
6 6 # If we create a QApplication, keep a reference to it so that it doesn't get
7 7 # garbage collected.
8 8 _appref = None
9 9 _already_warned = False
10 10
11 11
12 12 def _exec(obj):
13 13 # exec on PyQt6, exec_ elsewhere.
14 14 obj.exec() if hasattr(obj, "exec") else obj.exec_()
15 15
16 16
17 17 def _reclaim_excepthook():
18 18 shell = get_ipython()
19 19 if shell is not None:
20 20 sys.excepthook = shell.excepthook
21 21
22 22
23 announced = 0
24
25
26 23 def inputhook(context):
27 24 global _appref
28 25 app = QtCore.QCoreApplication.instance()
29 26 if not app:
30 27 if sys.platform == 'linux':
31 28 if not os.environ.get('DISPLAY') \
32 29 and not os.environ.get('WAYLAND_DISPLAY'):
33 30 import warnings
34 31 global _already_warned
35 32 if not _already_warned:
36 33 _already_warned = True
37 34 warnings.warn(
38 35 'The DISPLAY or WAYLAND_DISPLAY environment variable is '
39 36 'not set or empty and Qt5 requires this environment '
40 37 'variable. Deactivate Qt5 code.'
41 38 )
42 39 return
43 40 try:
44 41 QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
45 42 except AttributeError: # Only for Qt>=5.6, <6.
46 43 pass
47 44 try:
48 45 QtCore.QApplication.setHighDpiScaleFactorRoundingPolicy(
49 46 QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
50 47 )
51 48 except AttributeError: # Only for Qt>=5.14.
52 49 pass
53 50 _appref = app = QtGui.QApplication([" "])
54 51
55 52 # "reclaim" IPython sys.excepthook after event loop starts
56 53 # without this, it defaults back to BaseIPythonApplication.excepthook
57 54 # and exceptions in the Qt event loop are rendered without traceback
58 55 # formatting and look like "bug in IPython".
59 56 QtCore.QTimer.singleShot(0, _reclaim_excepthook)
60 57
61 58 event_loop = QtCore.QEventLoop(app)
62 global announced
63 if announced == 0:
64 print(f"`inputhook` running Qt {QtCore.qVersion()} event loop.")
65 announced += 1
66 elif announced == 10:
67 announced = 0
68 else:
69 announced += 1
59
70 60 if sys.platform == 'win32':
71 61 # The QSocketNotifier method doesn't appear to work on Windows.
72 62 # Use polling instead.
73 63 timer = QtCore.QTimer()
74 64 timer.timeout.connect(event_loop.quit)
75 65 while not context.input_is_ready():
76 66 # NOTE: run the event loop, and after 50 ms, call `quit` to exit it.
77 67 timer.start(50) # 50 ms
78 68 _exec(event_loop)
79 69 timer.stop()
80 70 else:
81 71 # On POSIX platforms, we can use a file descriptor to quit the event
82 72 # loop when there is input ready to read.
83 73 notifier = QtCore.QSocketNotifier(
84 74 context.fileno(), enum_helper("QtCore.QSocketNotifier.Type").Read
85 75 )
86 76 try:
87 77 # connect the callback we care about before we turn it on
88 78 # lambda is necessary as PyQT inspect the function signature to know
89 79 # what arguments to pass to. See https://github.com/ipython/ipython/pull/12355
90 80 notifier.activated.connect(lambda: event_loop.exit())
91 81 notifier.setEnabled(True)
92 82 # only start the event loop we are not already flipped
93 83 if not context.input_is_ready():
94 84 _exec(event_loop)
95 85 finally:
96 86 notifier.setEnabled(False)
General Comments 0
You need to be logged in to leave comments. Login now