##// END OF EJS Templates
safer check for isatty...
Min RK -
Show More
@@ -1,484 +1,490 b''
1 1 """IPython terminal interface using prompt_toolkit"""
2 2 from __future__ import print_function
3 3
4 4 import os
5 5 import sys
6 6 import warnings
7 7 from warnings import warn
8 8
9 9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 10 from IPython.utils import io
11 11 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
12 12 from IPython.utils.terminal import toggle_set_term_title, set_term_title
13 13 from IPython.utils.process import abbrev_cwd
14 14 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum
15 15
16 16 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
17 17 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
18 18 from prompt_toolkit.history import InMemoryHistory
19 19 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
20 20 from prompt_toolkit.interface import CommandLineInterface
21 21 from prompt_toolkit.key_binding.manager import KeyBindingManager
22 22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 23 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
24 24
25 25 from pygments.styles import get_style_by_name, get_all_styles
26 26 from pygments.token import Token
27 27
28 28 from .debugger import TerminalPdb, Pdb
29 29 from .magics import TerminalMagics
30 30 from .pt_inputhooks import get_inputhook_func
31 31 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
32 32 from .ptutils import IPythonPTCompleter, IPythonPTLexer
33 33 from .shortcuts import register_ipython_shortcuts
34 34
35 35 DISPLAY_BANNER_DEPRECATED = object()
36 36
37 37
38 38 from pygments.style import Style
39 39
40 40 class _NoStyle(Style): pass
41 41
42 42
43 43
44 44 _style_overrides_light_bg = {
45 45 Token.Prompt: '#0000ff',
46 46 Token.PromptNum: '#0000ee bold',
47 47 Token.OutPrompt: '#cc0000',
48 48 Token.OutPromptNum: '#bb0000 bold',
49 49 }
50 50
51 51 _style_overrides_linux = {
52 52 Token.Prompt: '#00cc00',
53 53 Token.PromptNum: '#00bb00 bold',
54 54 Token.OutPrompt: '#cc0000',
55 55 Token.OutPromptNum: '#bb0000 bold',
56 56 }
57 57
58 58
59 59
60 60 def get_default_editor():
61 61 try:
62 62 ed = os.environ['EDITOR']
63 63 if not PY3:
64 64 ed = ed.decode()
65 65 return ed
66 66 except KeyError:
67 67 pass
68 68 except UnicodeError:
69 69 warn("$EDITOR environment variable is not pure ASCII. Using platform "
70 70 "default editor.")
71 71
72 72 if os.name == 'posix':
73 73 return 'vi' # the only one guaranteed to be there!
74 74 else:
75 75 return 'notepad' # same in Windows!
76 76
77
78 if sys.stdin and sys.stdout and sys.stderr:
79 _is_tty = (sys.stdin.isatty()) and (sys.stdout.isatty()) and (sys.stderr.isatty())
77 # conservatively check for tty
78 # overridden streams can result in things like:
79 # - sys.stdin = None
80 # - no isatty method
81 for _name in ('stdin', 'stdout', 'stderr'):
82 _stream = getattr(sys, _name)
83 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
84 _is_tty = False
85 break
80 86 else:
81 _is_tty = False
87 _is_tty = True
82 88
83 89
84 90 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
85 91
86 92 class TerminalInteractiveShell(InteractiveShell):
87 93 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
88 94 'to reserve for the completion menu'
89 95 ).tag(config=True)
90 96
91 97 def _space_for_menu_changed(self, old, new):
92 98 self._update_layout()
93 99
94 100 pt_cli = None
95 101 debugger_history = None
96 102 _pt_app = None
97 103
98 104 simple_prompt = Bool(_use_simple_prompt,
99 105 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
100 106
101 107 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
102 108 IPython own testing machinery, and emacs inferior-shell integration through elpy.
103 109
104 110 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
105 111 environment variable is set, or the current terminal is not a tty.
106 112
107 113 """
108 114 ).tag(config=True)
109 115
110 116 @property
111 117 def debugger_cls(self):
112 118 return Pdb if self.simple_prompt else TerminalPdb
113 119
114 120 confirm_exit = Bool(True,
115 121 help="""
116 122 Set to confirm when you try to exit IPython with an EOF (Control-D
117 123 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
118 124 you can force a direct exit without any confirmation.""",
119 125 ).tag(config=True)
120 126
121 127 editing_mode = Unicode('emacs',
122 128 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
123 129 ).tag(config=True)
124 130
125 131 mouse_support = Bool(False,
126 132 help="Enable mouse support in the prompt"
127 133 ).tag(config=True)
128 134
129 135 highlighting_style = Unicode('legacy',
130 136 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
131 137 ).tag(config=True)
132 138
133 139
134 140 @observe('highlighting_style')
135 141 @observe('colors')
136 142 def _highlighting_style_changed(self, change):
137 143 self.refresh_style()
138 144
139 145 def refresh_style(self):
140 146 self._style = self._make_style_from_name(self.highlighting_style)
141 147
142 148
143 149 highlighting_style_overrides = Dict(
144 150 help="Override highlighting format for specific tokens"
145 151 ).tag(config=True)
146 152
147 153 true_color = Bool(False,
148 154 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
149 155 "If your terminal supports true color, the following command "
150 156 "should print 'TRUECOLOR' in orange: "
151 157 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
152 158 ).tag(config=True)
153 159
154 160 editor = Unicode(get_default_editor(),
155 161 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
156 162 ).tag(config=True)
157 163
158 164 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
159 165
160 166 prompts = Instance(Prompts)
161 167
162 168 @default('prompts')
163 169 def _prompts_default(self):
164 170 return self.prompts_class(self)
165 171
166 172 @observe('prompts')
167 173 def _(self, change):
168 174 self._update_layout()
169 175
170 176 @default('displayhook_class')
171 177 def _displayhook_class_default(self):
172 178 return RichPromptDisplayHook
173 179
174 180 term_title = Bool(True,
175 181 help="Automatically set the terminal title"
176 182 ).tag(config=True)
177 183
178 184 display_completions = Enum(('column', 'multicolumn','readlinelike'),
179 185 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
180 186 "'readlinelike'. These options are for `prompt_toolkit`, see "
181 187 "`prompt_toolkit` documentation for more information."
182 188 ),
183 189 default_value='multicolumn').tag(config=True)
184 190
185 191 highlight_matching_brackets = Bool(True,
186 192 help="Highlight matching brackets .",
187 193 ).tag(config=True)
188 194
189 195 @observe('term_title')
190 196 def init_term_title(self, change=None):
191 197 # Enable or disable the terminal title.
192 198 if self.term_title:
193 199 toggle_set_term_title(True)
194 200 set_term_title('IPython: ' + abbrev_cwd())
195 201 else:
196 202 toggle_set_term_title(False)
197 203
198 204 def init_display_formatter(self):
199 205 super(TerminalInteractiveShell, self).init_display_formatter()
200 206 # terminal only supports plain text
201 207 self.display_formatter.active_types = ['text/plain']
202 208
203 209 def init_prompt_toolkit_cli(self):
204 210 if self.simple_prompt:
205 211 # Fall back to plain non-interactive output for tests.
206 212 # This is very limited, and only accepts a single line.
207 213 def prompt():
208 214 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
209 215 self.prompt_for_code = prompt
210 216 return
211 217
212 218 # Set up keyboard shortcuts
213 219 kbmanager = KeyBindingManager.for_prompt()
214 220 register_ipython_shortcuts(kbmanager.registry, self)
215 221
216 222 # Pre-populate history from IPython's history database
217 223 history = InMemoryHistory()
218 224 last_cell = u""
219 225 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
220 226 include_latest=True):
221 227 # Ignore blank lines and consecutive duplicates
222 228 cell = cell.rstrip()
223 229 if cell and (cell != last_cell):
224 230 history.append(cell)
225 231
226 232 self._style = self._make_style_from_name(self.highlighting_style)
227 233 style = DynamicStyle(lambda: self._style)
228 234
229 235 editing_mode = getattr(EditingMode, self.editing_mode.upper())
230 236
231 237 self._pt_app = create_prompt_application(
232 238 editing_mode=editing_mode,
233 239 key_bindings_registry=kbmanager.registry,
234 240 history=history,
235 241 completer=IPythonPTCompleter(self.Completer),
236 242 enable_history_search=True,
237 243 style=style,
238 244 mouse_support=self.mouse_support,
239 245 **self._layout_options()
240 246 )
241 247 self._eventloop = create_eventloop(self.inputhook)
242 248 self.pt_cli = CommandLineInterface(
243 249 self._pt_app, eventloop=self._eventloop,
244 250 output=create_output(true_color=self.true_color))
245 251
246 252 def _make_style_from_name(self, name):
247 253 """
248 254 Small wrapper that make an IPython compatible style from a style name
249 255
250 256 We need that to add style for prompt ... etc.
251 257 """
252 258 style_overrides = {}
253 259 if name == 'legacy':
254 260 legacy = self.colors.lower()
255 261 if legacy == 'linux':
256 262 style_cls = get_style_by_name('monokai')
257 263 style_overrides = _style_overrides_linux
258 264 elif legacy == 'lightbg':
259 265 style_overrides = _style_overrides_light_bg
260 266 style_cls = get_style_by_name('pastie')
261 267 elif legacy == 'neutral':
262 268 # The default theme needs to be visible on both a dark background
263 269 # and a light background, because we can't tell what the terminal
264 270 # looks like. These tweaks to the default theme help with that.
265 271 style_cls = get_style_by_name('default')
266 272 style_overrides.update({
267 273 Token.Number: '#007700',
268 274 Token.Operator: 'noinherit',
269 275 Token.String: '#BB6622',
270 276 Token.Name.Function: '#2080D0',
271 277 Token.Name.Class: 'bold #2080D0',
272 278 Token.Name.Namespace: 'bold #2080D0',
273 279 Token.Prompt: '#009900',
274 280 Token.PromptNum: '#00ff00 bold',
275 281 Token.OutPrompt: '#990000',
276 282 Token.OutPromptNum: '#ff0000 bold',
277 283 })
278 284 elif legacy =='nocolor':
279 285 style_cls=_NoStyle
280 286 style_overrides = {}
281 287 else :
282 288 raise ValueError('Got unknown colors: ', legacy)
283 289 else :
284 290 style_cls = get_style_by_name(name)
285 291 style_overrides = {
286 292 Token.Prompt: '#009900',
287 293 Token.PromptNum: '#00ff00 bold',
288 294 Token.OutPrompt: '#990000',
289 295 Token.OutPromptNum: '#ff0000 bold',
290 296 }
291 297 style_overrides.update(self.highlighting_style_overrides)
292 298 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
293 299 style_dict=style_overrides)
294 300
295 301 return style
296 302
297 303 def _layout_options(self):
298 304 """
299 305 Return the current layout option for the current Terminal InteractiveShell
300 306 """
301 307 return {
302 308 'lexer':IPythonPTLexer(),
303 309 'reserve_space_for_menu':self.space_for_menu,
304 310 'get_prompt_tokens':self.prompts.in_prompt_tokens,
305 311 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
306 312 'multiline':True,
307 313 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
308 314
309 315 # Highlight matching brackets, but only when this setting is
310 316 # enabled, and only when the DEFAULT_BUFFER has the focus.
311 317 'extra_input_processors': [ConditionalProcessor(
312 318 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
313 319 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
314 320 Condition(lambda cli: self.highlight_matching_brackets))],
315 321 }
316 322
317 323 def _update_layout(self):
318 324 """
319 325 Ask for a re computation of the application layout, if for example ,
320 326 some configuration options have changed.
321 327 """
322 328 if self._pt_app:
323 329 self._pt_app.layout = create_prompt_layout(**self._layout_options())
324 330
325 331 def prompt_for_code(self):
326 332 document = self.pt_cli.run(
327 333 pre_run=self.pre_prompt, reset_current_buffer=True)
328 334 return document.text
329 335
330 336 def enable_win_unicode_console(self):
331 337 import win_unicode_console
332 338
333 339 if PY3:
334 340 win_unicode_console.enable()
335 341 else:
336 342 # https://github.com/ipython/ipython/issues/9768
337 343 from win_unicode_console.streams import (TextStreamWrapper,
338 344 stdout_text_transcoded, stderr_text_transcoded)
339 345
340 346 class LenientStrStreamWrapper(TextStreamWrapper):
341 347 def write(self, s):
342 348 if isinstance(s, bytes):
343 349 s = s.decode(self.encoding, 'replace')
344 350
345 351 self.base.write(s)
346 352
347 353 stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded)
348 354 stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded)
349 355
350 356 win_unicode_console.enable(stdout=stdout_text_str,
351 357 stderr=stderr_text_str)
352 358
353 359 def init_io(self):
354 360 if sys.platform not in {'win32', 'cli'}:
355 361 return
356 362
357 363 self.enable_win_unicode_console()
358 364
359 365 import colorama
360 366 colorama.init()
361 367
362 368 # For some reason we make these wrappers around stdout/stderr.
363 369 # For now, we need to reset them so all output gets coloured.
364 370 # https://github.com/ipython/ipython/issues/8669
365 371 # io.std* are deprecated, but don't show our own deprecation warnings
366 372 # during initialization of the deprecated API.
367 373 with warnings.catch_warnings():
368 374 warnings.simplefilter('ignore', DeprecationWarning)
369 375 io.stdout = io.IOStream(sys.stdout)
370 376 io.stderr = io.IOStream(sys.stderr)
371 377
372 378 def init_magics(self):
373 379 super(TerminalInteractiveShell, self).init_magics()
374 380 self.register_magics(TerminalMagics)
375 381
376 382 def init_alias(self):
377 383 # The parent class defines aliases that can be safely used with any
378 384 # frontend.
379 385 super(TerminalInteractiveShell, self).init_alias()
380 386
381 387 # Now define aliases that only make sense on the terminal, because they
382 388 # need direct access to the console in a way that we can't emulate in
383 389 # GUI or web frontend
384 390 if os.name == 'posix':
385 391 for cmd in ['clear', 'more', 'less', 'man']:
386 392 self.alias_manager.soft_define_alias(cmd, cmd)
387 393
388 394
389 395 def __init__(self, *args, **kwargs):
390 396 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
391 397 self.init_prompt_toolkit_cli()
392 398 self.init_term_title()
393 399 self.keep_running = True
394 400
395 401 self.debugger_history = InMemoryHistory()
396 402
397 403 def ask_exit(self):
398 404 self.keep_running = False
399 405
400 406 rl_next_input = None
401 407
402 408 def pre_prompt(self):
403 409 if self.rl_next_input:
404 410 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
405 411 self.rl_next_input = None
406 412
407 413 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
408 414
409 415 if display_banner is not DISPLAY_BANNER_DEPRECATED:
410 416 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
411 417
412 418 while self.keep_running:
413 419 print(self.separate_in, end='')
414 420
415 421 try:
416 422 code = self.prompt_for_code()
417 423 except EOFError:
418 424 if (not self.confirm_exit) \
419 425 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
420 426 self.ask_exit()
421 427
422 428 else:
423 429 if code:
424 430 self.run_cell(code, store_history=True)
425 431
426 432 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
427 433 # An extra layer of protection in case someone mashing Ctrl-C breaks
428 434 # out of our internal code.
429 435 if display_banner is not DISPLAY_BANNER_DEPRECATED:
430 436 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
431 437 while True:
432 438 try:
433 439 self.interact()
434 440 break
435 441 except KeyboardInterrupt:
436 442 print("\nKeyboardInterrupt escaped interact()\n")
437 443
438 444 if hasattr(self, '_eventloop'):
439 445 self._eventloop.close()
440 446
441 447 _inputhook = None
442 448 def inputhook(self, context):
443 449 if self._inputhook is not None:
444 450 self._inputhook(context)
445 451
446 452 def enable_gui(self, gui=None):
447 453 if gui:
448 454 self._inputhook = get_inputhook_func(gui)
449 455 else:
450 456 self._inputhook = None
451 457
452 458 # Run !system commands directly, not through pipes, so terminal programs
453 459 # work correctly.
454 460 system = InteractiveShell.system_raw
455 461
456 462 def auto_rewrite_input(self, cmd):
457 463 """Overridden from the parent class to use fancy rewriting prompt"""
458 464 if not self.show_rewritten_input:
459 465 return
460 466
461 467 tokens = self.prompts.rewrite_prompt_tokens()
462 468 if self.pt_cli:
463 469 self.pt_cli.print_tokens(tokens)
464 470 print(cmd)
465 471 else:
466 472 prompt = ''.join(s for t, s in tokens)
467 473 print(prompt, cmd, sep='')
468 474
469 475 _prompts_before = None
470 476 def switch_doctest_mode(self, mode):
471 477 """Switch prompts to classic for %doctest_mode"""
472 478 if mode:
473 479 self._prompts_before = self.prompts
474 480 self.prompts = ClassicPrompts(self)
475 481 elif self._prompts_before:
476 482 self.prompts = self._prompts_before
477 483 self._prompts_before = None
478 484 self._update_layout()
479 485
480 486
481 487 InteractiveShellABC.register(TerminalInteractiveShell)
482 488
483 489 if __name__ == '__main__':
484 490 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now