##// END OF EJS Templates
Use new input transformation API in %paste...
Thomas Kluyver -
Show More
@@ -1,754 +1,753 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Subclass of InteractiveShell for terminal based frontends."""
2 """Subclass of InteractiveShell for terminal based frontends."""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 import bdb
18 import bdb
19 import os
19 import os
20 import re
20 import re
21 import sys
21 import sys
22 import textwrap
22 import textwrap
23
23
24 # We need to use nested to support python 2.6, once we move to >=2.7, we can
24 # We need to use nested to support python 2.6, once we move to >=2.7, we can
25 # use the with keyword's new builtin support for nested managers
25 # use the with keyword's new builtin support for nested managers
26 try:
26 try:
27 from contextlib import nested
27 from contextlib import nested
28 except:
28 except:
29 from IPython.utils.nested_context import nested
29 from IPython.utils.nested_context import nested
30
30
31 from IPython.core.error import TryNext, UsageError
31 from IPython.core.error import TryNext, UsageError
32 from IPython.core.usage import interactive_usage, default_banner
32 from IPython.core.usage import interactive_usage, default_banner
33 from IPython.core.inputsplitter import IPythonInputSplitter
33 from IPython.core.inputsplitter import IPythonInputSplitter
34 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
34 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
35 from IPython.core.magic import Magics, magics_class, line_magic
35 from IPython.core.magic import Magics, magics_class, line_magic
36 from IPython.testing.skipdoctest import skip_doctest
36 from IPython.testing.skipdoctest import skip_doctest
37 from IPython.utils.encoding import get_stream_enc
37 from IPython.utils.encoding import get_stream_enc
38 from IPython.utils import py3compat
38 from IPython.utils import py3compat
39 from IPython.utils.terminal import toggle_set_term_title, set_term_title
39 from IPython.utils.terminal import toggle_set_term_title, set_term_title
40 from IPython.utils.process import abbrev_cwd
40 from IPython.utils.process import abbrev_cwd
41 from IPython.utils.warn import warn, error
41 from IPython.utils.warn import warn, error
42 from IPython.utils.text import num_ini_spaces, SList, strip_email_quotes
42 from IPython.utils.text import num_ini_spaces, SList, strip_email_quotes
43 from IPython.utils.traitlets import Integer, CBool, Unicode
43 from IPython.utils.traitlets import Integer, CBool, Unicode
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Utilities
46 # Utilities
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 def get_default_editor():
49 def get_default_editor():
50 try:
50 try:
51 ed = os.environ['EDITOR']
51 ed = os.environ['EDITOR']
52 except KeyError:
52 except KeyError:
53 if os.name == 'posix':
53 if os.name == 'posix':
54 ed = 'vi' # the only one guaranteed to be there!
54 ed = 'vi' # the only one guaranteed to be there!
55 else:
55 else:
56 ed = 'notepad' # same in Windows!
56 ed = 'notepad' # same in Windows!
57 return ed
57 return ed
58
58
59
59
60 def get_pasted_lines(sentinel, l_input=py3compat.input):
60 def get_pasted_lines(sentinel, l_input=py3compat.input):
61 """ Yield pasted lines until the user enters the given sentinel value.
61 """ Yield pasted lines until the user enters the given sentinel value.
62 """
62 """
63 print("Pasting code; enter '%s' alone on the line to stop or use Ctrl-D." \
63 print("Pasting code; enter '%s' alone on the line to stop or use Ctrl-D." \
64 % sentinel)
64 % sentinel)
65 while True:
65 while True:
66 try:
66 try:
67 l = l_input(':')
67 l = l_input(':')
68 if l == sentinel:
68 if l == sentinel:
69 return
69 return
70 else:
70 else:
71 yield l
71 yield l
72 except EOFError:
72 except EOFError:
73 print('<EOF>')
73 print('<EOF>')
74 return
74 return
75
75
76
76
77 #------------------------------------------------------------------------
77 #------------------------------------------------------------------------
78 # Terminal-specific magics
78 # Terminal-specific magics
79 #------------------------------------------------------------------------
79 #------------------------------------------------------------------------
80
80
81 @magics_class
81 @magics_class
82 class TerminalMagics(Magics):
82 class TerminalMagics(Magics):
83 def __init__(self, shell):
83 def __init__(self, shell):
84 super(TerminalMagics, self).__init__(shell)
84 super(TerminalMagics, self).__init__(shell)
85 self.input_splitter = IPythonInputSplitter()
85 self.input_splitter = IPythonInputSplitter()
86
86
87 def cleanup_input(self, block):
87 def cleanup_input(self, block):
88 """Apply all possible IPython cleanups to an input block.
88 """Apply all possible IPython cleanups to an input block.
89
89
90 This means:
90 This means:
91
91
92 - remove any global leading whitespace (dedent)
92 - remove any global leading whitespace (dedent)
93 - remove any email quotes ('>') if they are present in *all* lines
93 - remove any email quotes ('>') if they are present in *all* lines
94 - apply all static inputsplitter transforms and break into sub-blocks
94 - apply all static inputsplitter transforms and break into sub-blocks
95 - apply prefilter() to each sub-block that is a single line.
95 - apply prefilter() to each sub-block that is a single line.
96
96
97 Parameters
97 Parameters
98 ----------
98 ----------
99 block : str
99 block : str
100 A possibly multiline input string of code.
100 A possibly multiline input string of code.
101
101
102 Returns
102 Returns
103 -------
103 -------
104 transformed block : str
104 transformed block : str
105 The input, with all transformations above applied.
105 The input, with all transformations above applied.
106 """
106 """
107 # We have to effectively implement client-side the loop that is done by
107 # We have to effectively implement client-side the loop that is done by
108 # the terminal frontend, and furthermore do it on a block that can
108 # the terminal frontend, and furthermore do it on a block that can
109 # possibly contain multiple statments pasted in one go.
109 # possibly contain multiple statments pasted in one go.
110
110
111 # First, run the input through the block splitting code. We should
111 # First, run the input through the block splitting code. We should
112 # eventually make this a self-contained method in the inputsplitter.
112 # eventually make this a self-contained method in the inputsplitter.
113 isp = self.input_splitter
113 isp = self.input_splitter
114 isp.reset()
114 isp.reset()
115 b = textwrap.dedent(block)
115 b = textwrap.dedent(block)
116
116
117 # Remove email quotes first. These must be consistently applied to
117 # Remove email quotes first. These must be consistently applied to
118 # *all* lines to be removed
118 # *all* lines to be removed
119 b = strip_email_quotes(b)
119 b = strip_email_quotes(b)
120
120
121 # Split the input into independent sub-blocks so we can later do
121 # Split the input into independent sub-blocks so we can later do
122 # prefiltering (which must be done *only* to single-line inputs)
122 # prefiltering (which must be done *only* to single-line inputs)
123 blocks = []
123 blocks = []
124 last_block = []
124 last_block = []
125 for line in b.splitlines():
125 for line in b.splitlines():
126 isp.push(line)
126 isp.push(line)
127 last_block.append(line)
127 last_block.append(line)
128 if not isp.push_accepts_more():
128 if not isp.push_accepts_more():
129 blocks.append(isp.source_reset())
129 blocks.append(isp.source_reset())
130 last_block = []
130 last_block = []
131 if last_block:
131 if last_block:
132 blocks.append('\n'.join(last_block))
132 blocks.append('\n'.join(last_block))
133
133
134 # Now, apply prefiltering to any one-line block to match the behavior
134 # Now, apply prefiltering to any one-line block to match the behavior
135 # of the interactive terminal
135 # of the interactive terminal
136 final_blocks = []
136 final_blocks = []
137 for block in blocks:
137 for block in blocks:
138 lines = block.splitlines()
138 lines = block.splitlines()
139 if len(lines) == 1:
139 if len(lines) == 1:
140 final_blocks.append(self.shell.prefilter(lines[0]))
140 final_blocks.append(self.shell.prefilter(lines[0]))
141 else:
141 else:
142 final_blocks.append(block)
142 final_blocks.append(block)
143
143
144 # We now have the final version of the input code as a list of blocks,
144 # We now have the final version of the input code as a list of blocks,
145 # with all inputsplitter transformations applied and single-line blocks
145 # with all inputsplitter transformations applied and single-line blocks
146 # run through prefilter. For further processing, turn into a single
146 # run through prefilter. For further processing, turn into a single
147 # string as the rest of our apis use string inputs.
147 # string as the rest of our apis use string inputs.
148 return '\n'.join(final_blocks)
148 return '\n'.join(final_blocks)
149
149
150 def store_or_execute(self, block, name):
150 def store_or_execute(self, block, name):
151 """ Execute a block, or store it in a variable, per the user's request.
151 """ Execute a block, or store it in a variable, per the user's request.
152 """
152 """
153
154 b = self.cleanup_input(block)
155 if name:
153 if name:
156 # If storing it for further editing
154 # If storing it for further editing
157 self.shell.user_ns[name] = SList(b.splitlines())
155 self.shell.user_ns[name] = SList(block.splitlines())
158 print("Block assigned to '%s'" % name)
156 print("Block assigned to '%s'" % name)
159 else:
157 else:
160 self.shell.user_ns['pasted_block'] = b
158 self.shell.user_ns['pasted_block'] = block
159 b = self.shell.input_transformer_manager.transform_cell(block)
161 self.shell.using_paste_magics = True
160 self.shell.using_paste_magics = True
162 try:
161 try:
163 self.shell.run_cell(b)
162 self.shell.run_cell(b)
164 finally:
163 finally:
165 self.shell.using_paste_magics = False
164 self.shell.using_paste_magics = False
166
165
167 def rerun_pasted(self, name='pasted_block'):
166 def rerun_pasted(self, name='pasted_block'):
168 """ Rerun a previously pasted command.
167 """ Rerun a previously pasted command.
169 """
168 """
170 b = self.shell.user_ns.get(name)
169 b = self.shell.user_ns.get(name)
171
170
172 # Sanity checks
171 # Sanity checks
173 if b is None:
172 if b is None:
174 raise UsageError('No previous pasted block available')
173 raise UsageError('No previous pasted block available')
175 if not isinstance(b, basestring):
174 if not isinstance(b, basestring):
176 raise UsageError(
175 raise UsageError(
177 "Variable 'pasted_block' is not a string, can't execute")
176 "Variable 'pasted_block' is not a string, can't execute")
178
177
179 print("Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b)))
178 print("Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b)))
180 self.shell.run_cell(b)
179 self.shell.run_cell(b)
181
180
182 @line_magic
181 @line_magic
183 def autoindent(self, parameter_s = ''):
182 def autoindent(self, parameter_s = ''):
184 """Toggle autoindent on/off (if available)."""
183 """Toggle autoindent on/off (if available)."""
185
184
186 self.shell.set_autoindent()
185 self.shell.set_autoindent()
187 print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent])
186 print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent])
188
187
189 @skip_doctest
188 @skip_doctest
190 @line_magic
189 @line_magic
191 def cpaste(self, parameter_s=''):
190 def cpaste(self, parameter_s=''):
192 """Paste & execute a pre-formatted code block from clipboard.
191 """Paste & execute a pre-formatted code block from clipboard.
193
192
194 You must terminate the block with '--' (two minus-signs) or Ctrl-D
193 You must terminate the block with '--' (two minus-signs) or Ctrl-D
195 alone on the line. You can also provide your own sentinel with '%paste
194 alone on the line. You can also provide your own sentinel with '%paste
196 -s %%' ('%%' is the new sentinel for this operation)
195 -s %%' ('%%' is the new sentinel for this operation)
197
196
198 The block is dedented prior to execution to enable execution of method
197 The block is dedented prior to execution to enable execution of method
199 definitions. '>' and '+' characters at the beginning of a line are
198 definitions. '>' and '+' characters at the beginning of a line are
200 ignored, to allow pasting directly from e-mails, diff files and
199 ignored, to allow pasting directly from e-mails, diff files and
201 doctests (the '...' continuation prompt is also stripped). The
200 doctests (the '...' continuation prompt is also stripped). The
202 executed block is also assigned to variable named 'pasted_block' for
201 executed block is also assigned to variable named 'pasted_block' for
203 later editing with '%edit pasted_block'.
202 later editing with '%edit pasted_block'.
204
203
205 You can also pass a variable name as an argument, e.g. '%cpaste foo'.
204 You can also pass a variable name as an argument, e.g. '%cpaste foo'.
206 This assigns the pasted block to variable 'foo' as string, without
205 This assigns the pasted block to variable 'foo' as string, without
207 dedenting or executing it (preceding >>> and + is still stripped)
206 dedenting or executing it (preceding >>> and + is still stripped)
208
207
209 '%cpaste -r' re-executes the block previously entered by cpaste.
208 '%cpaste -r' re-executes the block previously entered by cpaste.
210
209
211 Do not be alarmed by garbled output on Windows (it's a readline bug).
210 Do not be alarmed by garbled output on Windows (it's a readline bug).
212 Just press enter and type -- (and press enter again) and the block
211 Just press enter and type -- (and press enter again) and the block
213 will be what was just pasted.
212 will be what was just pasted.
214
213
215 IPython statements (magics, shell escapes) are not supported (yet).
214 IPython statements (magics, shell escapes) are not supported (yet).
216
215
217 See also
216 See also
218 --------
217 --------
219 paste: automatically pull code from clipboard.
218 paste: automatically pull code from clipboard.
220
219
221 Examples
220 Examples
222 --------
221 --------
223 ::
222 ::
224
223
225 In [8]: %cpaste
224 In [8]: %cpaste
226 Pasting code; enter '--' alone on the line to stop.
225 Pasting code; enter '--' alone on the line to stop.
227 :>>> a = ["world!", "Hello"]
226 :>>> a = ["world!", "Hello"]
228 :>>> print " ".join(sorted(a))
227 :>>> print " ".join(sorted(a))
229 :--
228 :--
230 Hello world!
229 Hello world!
231 """
230 """
232 opts, name = self.parse_options(parameter_s, 'rs:', mode='string')
231 opts, name = self.parse_options(parameter_s, 'rs:', mode='string')
233 if 'r' in opts:
232 if 'r' in opts:
234 self.rerun_pasted()
233 self.rerun_pasted()
235 return
234 return
236
235
237 sentinel = opts.get('s', '--')
236 sentinel = opts.get('s', '--')
238 block = '\n'.join(get_pasted_lines(sentinel))
237 block = '\n'.join(get_pasted_lines(sentinel))
239 self.store_or_execute(block, name)
238 self.store_or_execute(block, name)
240
239
241 @line_magic
240 @line_magic
242 def paste(self, parameter_s=''):
241 def paste(self, parameter_s=''):
243 """Paste & execute a pre-formatted code block from clipboard.
242 """Paste & execute a pre-formatted code block from clipboard.
244
243
245 The text is pulled directly from the clipboard without user
244 The text is pulled directly from the clipboard without user
246 intervention and printed back on the screen before execution (unless
245 intervention and printed back on the screen before execution (unless
247 the -q flag is given to force quiet mode).
246 the -q flag is given to force quiet mode).
248
247
249 The block is dedented prior to execution to enable execution of method
248 The block is dedented prior to execution to enable execution of method
250 definitions. '>' and '+' characters at the beginning of a line are
249 definitions. '>' and '+' characters at the beginning of a line are
251 ignored, to allow pasting directly from e-mails, diff files and
250 ignored, to allow pasting directly from e-mails, diff files and
252 doctests (the '...' continuation prompt is also stripped). The
251 doctests (the '...' continuation prompt is also stripped). The
253 executed block is also assigned to variable named 'pasted_block' for
252 executed block is also assigned to variable named 'pasted_block' for
254 later editing with '%edit pasted_block'.
253 later editing with '%edit pasted_block'.
255
254
256 You can also pass a variable name as an argument, e.g. '%paste foo'.
255 You can also pass a variable name as an argument, e.g. '%paste foo'.
257 This assigns the pasted block to variable 'foo' as string, without
256 This assigns the pasted block to variable 'foo' as string, without
258 executing it (preceding >>> and + is still stripped).
257 executing it (preceding >>> and + is still stripped).
259
258
260 Options
259 Options
261 -------
260 -------
262
261
263 -r: re-executes the block previously entered by cpaste.
262 -r: re-executes the block previously entered by cpaste.
264
263
265 -q: quiet mode: do not echo the pasted text back to the terminal.
264 -q: quiet mode: do not echo the pasted text back to the terminal.
266
265
267 IPython statements (magics, shell escapes) are not supported (yet).
266 IPython statements (magics, shell escapes) are not supported (yet).
268
267
269 See also
268 See also
270 --------
269 --------
271 cpaste: manually paste code into terminal until you mark its end.
270 cpaste: manually paste code into terminal until you mark its end.
272 """
271 """
273 opts, name = self.parse_options(parameter_s, 'rq', mode='string')
272 opts, name = self.parse_options(parameter_s, 'rq', mode='string')
274 if 'r' in opts:
273 if 'r' in opts:
275 self.rerun_pasted()
274 self.rerun_pasted()
276 return
275 return
277 try:
276 try:
278 block = self.shell.hooks.clipboard_get()
277 block = self.shell.hooks.clipboard_get()
279 except TryNext as clipboard_exc:
278 except TryNext as clipboard_exc:
280 message = getattr(clipboard_exc, 'args')
279 message = getattr(clipboard_exc, 'args')
281 if message:
280 if message:
282 error(message[0])
281 error(message[0])
283 else:
282 else:
284 error('Could not get text from the clipboard.')
283 error('Could not get text from the clipboard.')
285 return
284 return
286
285
287 # By default, echo back to terminal unless quiet mode is requested
286 # By default, echo back to terminal unless quiet mode is requested
288 if 'q' not in opts:
287 if 'q' not in opts:
289 write = self.shell.write
288 write = self.shell.write
290 write(self.shell.pycolorize(block))
289 write(self.shell.pycolorize(block))
291 if not block.endswith('\n'):
290 if not block.endswith('\n'):
292 write('\n')
291 write('\n')
293 write("## -- End pasted text --\n")
292 write("## -- End pasted text --\n")
294
293
295 self.store_or_execute(block, name)
294 self.store_or_execute(block, name)
296
295
297 # Class-level: add a '%cls' magic only on Windows
296 # Class-level: add a '%cls' magic only on Windows
298 if sys.platform == 'win32':
297 if sys.platform == 'win32':
299 @line_magic
298 @line_magic
300 def cls(self, s):
299 def cls(self, s):
301 """Clear screen.
300 """Clear screen.
302 """
301 """
303 os.system("cls")
302 os.system("cls")
304
303
305 #-----------------------------------------------------------------------------
304 #-----------------------------------------------------------------------------
306 # Main class
305 # Main class
307 #-----------------------------------------------------------------------------
306 #-----------------------------------------------------------------------------
308
307
309 class TerminalInteractiveShell(InteractiveShell):
308 class TerminalInteractiveShell(InteractiveShell):
310
309
311 autoedit_syntax = CBool(False, config=True,
310 autoedit_syntax = CBool(False, config=True,
312 help="auto editing of files with syntax errors.")
311 help="auto editing of files with syntax errors.")
313 banner = Unicode('')
312 banner = Unicode('')
314 banner1 = Unicode(default_banner, config=True,
313 banner1 = Unicode(default_banner, config=True,
315 help="""The part of the banner to be printed before the profile"""
314 help="""The part of the banner to be printed before the profile"""
316 )
315 )
317 banner2 = Unicode('', config=True,
316 banner2 = Unicode('', config=True,
318 help="""The part of the banner to be printed after the profile"""
317 help="""The part of the banner to be printed after the profile"""
319 )
318 )
320 confirm_exit = CBool(True, config=True,
319 confirm_exit = CBool(True, config=True,
321 help="""
320 help="""
322 Set to confirm when you try to exit IPython with an EOF (Control-D
321 Set to confirm when you try to exit IPython with an EOF (Control-D
323 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
322 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
324 you can force a direct exit without any confirmation.""",
323 you can force a direct exit without any confirmation.""",
325 )
324 )
326 # This display_banner only controls whether or not self.show_banner()
325 # This display_banner only controls whether or not self.show_banner()
327 # is called when mainloop/interact are called. The default is False
326 # is called when mainloop/interact are called. The default is False
328 # because for the terminal based application, the banner behavior
327 # because for the terminal based application, the banner behavior
329 # is controlled by Global.display_banner, which IPythonApp looks at
328 # is controlled by Global.display_banner, which IPythonApp looks at
330 # to determine if *it* should call show_banner() by hand or not.
329 # to determine if *it* should call show_banner() by hand or not.
331 display_banner = CBool(False) # This isn't configurable!
330 display_banner = CBool(False) # This isn't configurable!
332 embedded = CBool(False)
331 embedded = CBool(False)
333 embedded_active = CBool(False)
332 embedded_active = CBool(False)
334 editor = Unicode(get_default_editor(), config=True,
333 editor = Unicode(get_default_editor(), config=True,
335 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
334 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
336 )
335 )
337 pager = Unicode('less', config=True,
336 pager = Unicode('less', config=True,
338 help="The shell program to be used for paging.")
337 help="The shell program to be used for paging.")
339
338
340 screen_length = Integer(0, config=True,
339 screen_length = Integer(0, config=True,
341 help=
340 help=
342 """Number of lines of your screen, used to control printing of very
341 """Number of lines of your screen, used to control printing of very
343 long strings. Strings longer than this number of lines will be sent
342 long strings. Strings longer than this number of lines will be sent
344 through a pager instead of directly printed. The default value for
343 through a pager instead of directly printed. The default value for
345 this is 0, which means IPython will auto-detect your screen size every
344 this is 0, which means IPython will auto-detect your screen size every
346 time it needs to print certain potentially long strings (this doesn't
345 time it needs to print certain potentially long strings (this doesn't
347 change the behavior of the 'print' keyword, it's only triggered
346 change the behavior of the 'print' keyword, it's only triggered
348 internally). If for some reason this isn't working well (it needs
347 internally). If for some reason this isn't working well (it needs
349 curses support), specify it yourself. Otherwise don't change the
348 curses support), specify it yourself. Otherwise don't change the
350 default.""",
349 default.""",
351 )
350 )
352 term_title = CBool(False, config=True,
351 term_title = CBool(False, config=True,
353 help="Enable auto setting the terminal title."
352 help="Enable auto setting the terminal title."
354 )
353 )
355
354
356 # This `using_paste_magics` is used to detect whether the code is being
355 # This `using_paste_magics` is used to detect whether the code is being
357 # executed via paste magics functions
356 # executed via paste magics functions
358 using_paste_magics = CBool(False)
357 using_paste_magics = CBool(False)
359
358
360 # In the terminal, GUI control is done via PyOS_InputHook
359 # In the terminal, GUI control is done via PyOS_InputHook
361 @staticmethod
360 @staticmethod
362 def enable_gui(gui=None, app=None):
361 def enable_gui(gui=None, app=None):
363 """Switch amongst GUI input hooks by name.
362 """Switch amongst GUI input hooks by name.
364 """
363 """
365 # Deferred import
364 # Deferred import
366 from IPython.lib.inputhook import enable_gui as real_enable_gui
365 from IPython.lib.inputhook import enable_gui as real_enable_gui
367 return real_enable_gui(gui, app)
366 return real_enable_gui(gui, app)
368
367
369 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
368 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
370 user_ns=None, user_module=None, custom_exceptions=((),None),
369 user_ns=None, user_module=None, custom_exceptions=((),None),
371 usage=None, banner1=None, banner2=None, display_banner=None,
370 usage=None, banner1=None, banner2=None, display_banner=None,
372 **kwargs):
371 **kwargs):
373
372
374 super(TerminalInteractiveShell, self).__init__(
373 super(TerminalInteractiveShell, self).__init__(
375 config=config, ipython_dir=ipython_dir, profile_dir=profile_dir, user_ns=user_ns,
374 config=config, ipython_dir=ipython_dir, profile_dir=profile_dir, user_ns=user_ns,
376 user_module=user_module, custom_exceptions=custom_exceptions,
375 user_module=user_module, custom_exceptions=custom_exceptions,
377 **kwargs
376 **kwargs
378 )
377 )
379 # use os.system instead of utils.process.system by default,
378 # use os.system instead of utils.process.system by default,
380 # because piped system doesn't make sense in the Terminal:
379 # because piped system doesn't make sense in the Terminal:
381 self.system = self.system_raw
380 self.system = self.system_raw
382
381
383 self.init_term_title()
382 self.init_term_title()
384 self.init_usage(usage)
383 self.init_usage(usage)
385 self.init_banner(banner1, banner2, display_banner)
384 self.init_banner(banner1, banner2, display_banner)
386
385
387 #-------------------------------------------------------------------------
386 #-------------------------------------------------------------------------
388 # Overrides of init stages
387 # Overrides of init stages
389 #-------------------------------------------------------------------------
388 #-------------------------------------------------------------------------
390
389
391 def init_display_formatter(self):
390 def init_display_formatter(self):
392 super(TerminalInteractiveShell, self).init_display_formatter()
391 super(TerminalInteractiveShell, self).init_display_formatter()
393 # terminal only supports plaintext
392 # terminal only supports plaintext
394 self.display_formatter.active_types = ['text/plain']
393 self.display_formatter.active_types = ['text/plain']
395
394
396 #-------------------------------------------------------------------------
395 #-------------------------------------------------------------------------
397 # Things related to the terminal
396 # Things related to the terminal
398 #-------------------------------------------------------------------------
397 #-------------------------------------------------------------------------
399
398
400 @property
399 @property
401 def usable_screen_length(self):
400 def usable_screen_length(self):
402 if self.screen_length == 0:
401 if self.screen_length == 0:
403 return 0
402 return 0
404 else:
403 else:
405 num_lines_bot = self.separate_in.count('\n')+1
404 num_lines_bot = self.separate_in.count('\n')+1
406 return self.screen_length - num_lines_bot
405 return self.screen_length - num_lines_bot
407
406
408 def init_term_title(self):
407 def init_term_title(self):
409 # Enable or disable the terminal title.
408 # Enable or disable the terminal title.
410 if self.term_title:
409 if self.term_title:
411 toggle_set_term_title(True)
410 toggle_set_term_title(True)
412 set_term_title('IPython: ' + abbrev_cwd())
411 set_term_title('IPython: ' + abbrev_cwd())
413 else:
412 else:
414 toggle_set_term_title(False)
413 toggle_set_term_title(False)
415
414
416 #-------------------------------------------------------------------------
415 #-------------------------------------------------------------------------
417 # Things related to aliases
416 # Things related to aliases
418 #-------------------------------------------------------------------------
417 #-------------------------------------------------------------------------
419
418
420 def init_alias(self):
419 def init_alias(self):
421 # The parent class defines aliases that can be safely used with any
420 # The parent class defines aliases that can be safely used with any
422 # frontend.
421 # frontend.
423 super(TerminalInteractiveShell, self).init_alias()
422 super(TerminalInteractiveShell, self).init_alias()
424
423
425 # Now define aliases that only make sense on the terminal, because they
424 # Now define aliases that only make sense on the terminal, because they
426 # need direct access to the console in a way that we can't emulate in
425 # need direct access to the console in a way that we can't emulate in
427 # GUI or web frontend
426 # GUI or web frontend
428 if os.name == 'posix':
427 if os.name == 'posix':
429 aliases = [('clear', 'clear'), ('more', 'more'), ('less', 'less'),
428 aliases = [('clear', 'clear'), ('more', 'more'), ('less', 'less'),
430 ('man', 'man')]
429 ('man', 'man')]
431 elif os.name == 'nt':
430 elif os.name == 'nt':
432 aliases = [('cls', 'cls')]
431 aliases = [('cls', 'cls')]
433
432
434
433
435 for name, cmd in aliases:
434 for name, cmd in aliases:
436 self.alias_manager.define_alias(name, cmd)
435 self.alias_manager.define_alias(name, cmd)
437
436
438 #-------------------------------------------------------------------------
437 #-------------------------------------------------------------------------
439 # Things related to the banner and usage
438 # Things related to the banner and usage
440 #-------------------------------------------------------------------------
439 #-------------------------------------------------------------------------
441
440
442 def _banner1_changed(self):
441 def _banner1_changed(self):
443 self.compute_banner()
442 self.compute_banner()
444
443
445 def _banner2_changed(self):
444 def _banner2_changed(self):
446 self.compute_banner()
445 self.compute_banner()
447
446
448 def _term_title_changed(self, name, new_value):
447 def _term_title_changed(self, name, new_value):
449 self.init_term_title()
448 self.init_term_title()
450
449
451 def init_banner(self, banner1, banner2, display_banner):
450 def init_banner(self, banner1, banner2, display_banner):
452 if banner1 is not None:
451 if banner1 is not None:
453 self.banner1 = banner1
452 self.banner1 = banner1
454 if banner2 is not None:
453 if banner2 is not None:
455 self.banner2 = banner2
454 self.banner2 = banner2
456 if display_banner is not None:
455 if display_banner is not None:
457 self.display_banner = display_banner
456 self.display_banner = display_banner
458 self.compute_banner()
457 self.compute_banner()
459
458
460 def show_banner(self, banner=None):
459 def show_banner(self, banner=None):
461 if banner is None:
460 if banner is None:
462 banner = self.banner
461 banner = self.banner
463 self.write(banner)
462 self.write(banner)
464
463
465 def compute_banner(self):
464 def compute_banner(self):
466 self.banner = self.banner1
465 self.banner = self.banner1
467 if self.profile and self.profile != 'default':
466 if self.profile and self.profile != 'default':
468 self.banner += '\nIPython profile: %s\n' % self.profile
467 self.banner += '\nIPython profile: %s\n' % self.profile
469 if self.banner2:
468 if self.banner2:
470 self.banner += '\n' + self.banner2
469 self.banner += '\n' + self.banner2
471
470
472 def init_usage(self, usage=None):
471 def init_usage(self, usage=None):
473 if usage is None:
472 if usage is None:
474 self.usage = interactive_usage
473 self.usage = interactive_usage
475 else:
474 else:
476 self.usage = usage
475 self.usage = usage
477
476
478 #-------------------------------------------------------------------------
477 #-------------------------------------------------------------------------
479 # Mainloop and code execution logic
478 # Mainloop and code execution logic
480 #-------------------------------------------------------------------------
479 #-------------------------------------------------------------------------
481
480
482 def mainloop(self, display_banner=None):
481 def mainloop(self, display_banner=None):
483 """Start the mainloop.
482 """Start the mainloop.
484
483
485 If an optional banner argument is given, it will override the
484 If an optional banner argument is given, it will override the
486 internally created default banner.
485 internally created default banner.
487 """
486 """
488
487
489 with nested(self.builtin_trap, self.display_trap):
488 with nested(self.builtin_trap, self.display_trap):
490
489
491 while 1:
490 while 1:
492 try:
491 try:
493 self.interact(display_banner=display_banner)
492 self.interact(display_banner=display_banner)
494 #self.interact_with_readline()
493 #self.interact_with_readline()
495 # XXX for testing of a readline-decoupled repl loop, call
494 # XXX for testing of a readline-decoupled repl loop, call
496 # interact_with_readline above
495 # interact_with_readline above
497 break
496 break
498 except KeyboardInterrupt:
497 except KeyboardInterrupt:
499 # this should not be necessary, but KeyboardInterrupt
498 # this should not be necessary, but KeyboardInterrupt
500 # handling seems rather unpredictable...
499 # handling seems rather unpredictable...
501 self.write("\nKeyboardInterrupt in interact()\n")
500 self.write("\nKeyboardInterrupt in interact()\n")
502
501
503 def _replace_rlhist_multiline(self, source_raw, hlen_before_cell):
502 def _replace_rlhist_multiline(self, source_raw, hlen_before_cell):
504 """Store multiple lines as a single entry in history"""
503 """Store multiple lines as a single entry in history"""
505
504
506 # do nothing without readline or disabled multiline
505 # do nothing without readline or disabled multiline
507 if not self.has_readline or not self.multiline_history:
506 if not self.has_readline or not self.multiline_history:
508 return hlen_before_cell
507 return hlen_before_cell
509
508
510 # windows rl has no remove_history_item
509 # windows rl has no remove_history_item
511 if not hasattr(self.readline, "remove_history_item"):
510 if not hasattr(self.readline, "remove_history_item"):
512 return hlen_before_cell
511 return hlen_before_cell
513
512
514 # skip empty cells
513 # skip empty cells
515 if not source_raw.rstrip():
514 if not source_raw.rstrip():
516 return hlen_before_cell
515 return hlen_before_cell
517
516
518 # nothing changed do nothing, e.g. when rl removes consecutive dups
517 # nothing changed do nothing, e.g. when rl removes consecutive dups
519 hlen = self.readline.get_current_history_length()
518 hlen = self.readline.get_current_history_length()
520 if hlen == hlen_before_cell:
519 if hlen == hlen_before_cell:
521 return hlen_before_cell
520 return hlen_before_cell
522
521
523 for i in range(hlen - hlen_before_cell):
522 for i in range(hlen - hlen_before_cell):
524 self.readline.remove_history_item(hlen - i - 1)
523 self.readline.remove_history_item(hlen - i - 1)
525 stdin_encoding = get_stream_enc(sys.stdin, 'utf-8')
524 stdin_encoding = get_stream_enc(sys.stdin, 'utf-8')
526 self.readline.add_history(py3compat.unicode_to_str(source_raw.rstrip(),
525 self.readline.add_history(py3compat.unicode_to_str(source_raw.rstrip(),
527 stdin_encoding))
526 stdin_encoding))
528 return self.readline.get_current_history_length()
527 return self.readline.get_current_history_length()
529
528
530 def interact(self, display_banner=None):
529 def interact(self, display_banner=None):
531 """Closely emulate the interactive Python console."""
530 """Closely emulate the interactive Python console."""
532
531
533 # batch run -> do not interact
532 # batch run -> do not interact
534 if self.exit_now:
533 if self.exit_now:
535 return
534 return
536
535
537 if display_banner is None:
536 if display_banner is None:
538 display_banner = self.display_banner
537 display_banner = self.display_banner
539
538
540 if isinstance(display_banner, basestring):
539 if isinstance(display_banner, basestring):
541 self.show_banner(display_banner)
540 self.show_banner(display_banner)
542 elif display_banner:
541 elif display_banner:
543 self.show_banner()
542 self.show_banner()
544
543
545 more = False
544 more = False
546
545
547 if self.has_readline:
546 if self.has_readline:
548 self.readline_startup_hook(self.pre_readline)
547 self.readline_startup_hook(self.pre_readline)
549 hlen_b4_cell = self.readline.get_current_history_length()
548 hlen_b4_cell = self.readline.get_current_history_length()
550 else:
549 else:
551 hlen_b4_cell = 0
550 hlen_b4_cell = 0
552 # exit_now is set by a call to %Exit or %Quit, through the
551 # exit_now is set by a call to %Exit or %Quit, through the
553 # ask_exit callback.
552 # ask_exit callback.
554
553
555 while not self.exit_now:
554 while not self.exit_now:
556 self.hooks.pre_prompt_hook()
555 self.hooks.pre_prompt_hook()
557 if more:
556 if more:
558 try:
557 try:
559 prompt = self.prompt_manager.render('in2')
558 prompt = self.prompt_manager.render('in2')
560 except:
559 except:
561 self.showtraceback()
560 self.showtraceback()
562 if self.autoindent:
561 if self.autoindent:
563 self.rl_do_indent = True
562 self.rl_do_indent = True
564
563
565 else:
564 else:
566 try:
565 try:
567 prompt = self.separate_in + self.prompt_manager.render('in')
566 prompt = self.separate_in + self.prompt_manager.render('in')
568 except:
567 except:
569 self.showtraceback()
568 self.showtraceback()
570 try:
569 try:
571 line = self.raw_input(prompt)
570 line = self.raw_input(prompt)
572 if self.exit_now:
571 if self.exit_now:
573 # quick exit on sys.std[in|out] close
572 # quick exit on sys.std[in|out] close
574 break
573 break
575 if self.autoindent:
574 if self.autoindent:
576 self.rl_do_indent = False
575 self.rl_do_indent = False
577
576
578 except KeyboardInterrupt:
577 except KeyboardInterrupt:
579 #double-guard against keyboardinterrupts during kbdint handling
578 #double-guard against keyboardinterrupts during kbdint handling
580 try:
579 try:
581 self.write('\nKeyboardInterrupt\n')
580 self.write('\nKeyboardInterrupt\n')
582 source_raw = self.input_splitter.source_raw_reset()[1]
581 source_raw = self.input_splitter.source_raw_reset()[1]
583 hlen_b4_cell = \
582 hlen_b4_cell = \
584 self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
583 self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
585 more = False
584 more = False
586 except KeyboardInterrupt:
585 except KeyboardInterrupt:
587 pass
586 pass
588 except EOFError:
587 except EOFError:
589 if self.autoindent:
588 if self.autoindent:
590 self.rl_do_indent = False
589 self.rl_do_indent = False
591 if self.has_readline:
590 if self.has_readline:
592 self.readline_startup_hook(None)
591 self.readline_startup_hook(None)
593 self.write('\n')
592 self.write('\n')
594 self.exit()
593 self.exit()
595 except bdb.BdbQuit:
594 except bdb.BdbQuit:
596 warn('The Python debugger has exited with a BdbQuit exception.\n'
595 warn('The Python debugger has exited with a BdbQuit exception.\n'
597 'Because of how pdb handles the stack, it is impossible\n'
596 'Because of how pdb handles the stack, it is impossible\n'
598 'for IPython to properly format this particular exception.\n'
597 'for IPython to properly format this particular exception.\n'
599 'IPython will resume normal operation.')
598 'IPython will resume normal operation.')
600 except:
599 except:
601 # exceptions here are VERY RARE, but they can be triggered
600 # exceptions here are VERY RARE, but they can be triggered
602 # asynchronously by signal handlers, for example.
601 # asynchronously by signal handlers, for example.
603 self.showtraceback()
602 self.showtraceback()
604 else:
603 else:
605 self.input_splitter.push(line)
604 self.input_splitter.push(line)
606 more = self.input_splitter.push_accepts_more()
605 more = self.input_splitter.push_accepts_more()
607 if (self.SyntaxTB.last_syntax_error and
606 if (self.SyntaxTB.last_syntax_error and
608 self.autoedit_syntax):
607 self.autoedit_syntax):
609 self.edit_syntax_error()
608 self.edit_syntax_error()
610 if not more:
609 if not more:
611 source_raw = self.input_splitter.source_raw_reset()[1]
610 source_raw = self.input_splitter.source_raw_reset()[1]
612 self.run_cell(source_raw, store_history=True)
611 self.run_cell(source_raw, store_history=True)
613 hlen_b4_cell = \
612 hlen_b4_cell = \
614 self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
613 self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
615
614
616 # Turn off the exit flag, so the mainloop can be restarted if desired
615 # Turn off the exit flag, so the mainloop can be restarted if desired
617 self.exit_now = False
616 self.exit_now = False
618
617
619 def raw_input(self, prompt=''):
618 def raw_input(self, prompt=''):
620 """Write a prompt and read a line.
619 """Write a prompt and read a line.
621
620
622 The returned line does not include the trailing newline.
621 The returned line does not include the trailing newline.
623 When the user enters the EOF key sequence, EOFError is raised.
622 When the user enters the EOF key sequence, EOFError is raised.
624
623
625 Optional inputs:
624 Optional inputs:
626
625
627 - prompt(''): a string to be printed to prompt the user.
626 - prompt(''): a string to be printed to prompt the user.
628
627
629 - continue_prompt(False): whether this line is the first one or a
628 - continue_prompt(False): whether this line is the first one or a
630 continuation in a sequence of inputs.
629 continuation in a sequence of inputs.
631 """
630 """
632 # Code run by the user may have modified the readline completer state.
631 # Code run by the user may have modified the readline completer state.
633 # We must ensure that our completer is back in place.
632 # We must ensure that our completer is back in place.
634
633
635 if self.has_readline:
634 if self.has_readline:
636 self.set_readline_completer()
635 self.set_readline_completer()
637
636
638 # raw_input expects str, but we pass it unicode sometimes
637 # raw_input expects str, but we pass it unicode sometimes
639 prompt = py3compat.cast_bytes_py2(prompt)
638 prompt = py3compat.cast_bytes_py2(prompt)
640
639
641 try:
640 try:
642 line = py3compat.str_to_unicode(self.raw_input_original(prompt))
641 line = py3compat.str_to_unicode(self.raw_input_original(prompt))
643 except ValueError:
642 except ValueError:
644 warn("\n********\nYou or a %run:ed script called sys.stdin.close()"
643 warn("\n********\nYou or a %run:ed script called sys.stdin.close()"
645 " or sys.stdout.close()!\nExiting IPython!\n")
644 " or sys.stdout.close()!\nExiting IPython!\n")
646 self.ask_exit()
645 self.ask_exit()
647 return ""
646 return ""
648
647
649 # Try to be reasonably smart about not re-indenting pasted input more
648 # Try to be reasonably smart about not re-indenting pasted input more
650 # than necessary. We do this by trimming out the auto-indent initial
649 # than necessary. We do this by trimming out the auto-indent initial
651 # spaces, if the user's actual input started itself with whitespace.
650 # spaces, if the user's actual input started itself with whitespace.
652 if self.autoindent:
651 if self.autoindent:
653 if num_ini_spaces(line) > self.indent_current_nsp:
652 if num_ini_spaces(line) > self.indent_current_nsp:
654 line = line[self.indent_current_nsp:]
653 line = line[self.indent_current_nsp:]
655 self.indent_current_nsp = 0
654 self.indent_current_nsp = 0
656
655
657 return line
656 return line
658
657
659 #-------------------------------------------------------------------------
658 #-------------------------------------------------------------------------
660 # Methods to support auto-editing of SyntaxErrors.
659 # Methods to support auto-editing of SyntaxErrors.
661 #-------------------------------------------------------------------------
660 #-------------------------------------------------------------------------
662
661
663 def edit_syntax_error(self):
662 def edit_syntax_error(self):
664 """The bottom half of the syntax error handler called in the main loop.
663 """The bottom half of the syntax error handler called in the main loop.
665
664
666 Loop until syntax error is fixed or user cancels.
665 Loop until syntax error is fixed or user cancels.
667 """
666 """
668
667
669 while self.SyntaxTB.last_syntax_error:
668 while self.SyntaxTB.last_syntax_error:
670 # copy and clear last_syntax_error
669 # copy and clear last_syntax_error
671 err = self.SyntaxTB.clear_err_state()
670 err = self.SyntaxTB.clear_err_state()
672 if not self._should_recompile(err):
671 if not self._should_recompile(err):
673 return
672 return
674 try:
673 try:
675 # may set last_syntax_error again if a SyntaxError is raised
674 # may set last_syntax_error again if a SyntaxError is raised
676 self.safe_execfile(err.filename,self.user_ns)
675 self.safe_execfile(err.filename,self.user_ns)
677 except:
676 except:
678 self.showtraceback()
677 self.showtraceback()
679 else:
678 else:
680 try:
679 try:
681 f = open(err.filename)
680 f = open(err.filename)
682 try:
681 try:
683 # This should be inside a display_trap block and I
682 # This should be inside a display_trap block and I
684 # think it is.
683 # think it is.
685 sys.displayhook(f.read())
684 sys.displayhook(f.read())
686 finally:
685 finally:
687 f.close()
686 f.close()
688 except:
687 except:
689 self.showtraceback()
688 self.showtraceback()
690
689
691 def _should_recompile(self,e):
690 def _should_recompile(self,e):
692 """Utility routine for edit_syntax_error"""
691 """Utility routine for edit_syntax_error"""
693
692
694 if e.filename in ('<ipython console>','<input>','<string>',
693 if e.filename in ('<ipython console>','<input>','<string>',
695 '<console>','<BackgroundJob compilation>',
694 '<console>','<BackgroundJob compilation>',
696 None):
695 None):
697
696
698 return False
697 return False
699 try:
698 try:
700 if (self.autoedit_syntax and
699 if (self.autoedit_syntax and
701 not self.ask_yes_no('Return to editor to correct syntax error? '
700 not self.ask_yes_no('Return to editor to correct syntax error? '
702 '[Y/n] ','y')):
701 '[Y/n] ','y')):
703 return False
702 return False
704 except EOFError:
703 except EOFError:
705 return False
704 return False
706
705
707 def int0(x):
706 def int0(x):
708 try:
707 try:
709 return int(x)
708 return int(x)
710 except TypeError:
709 except TypeError:
711 return 0
710 return 0
712 # always pass integer line and offset values to editor hook
711 # always pass integer line and offset values to editor hook
713 try:
712 try:
714 self.hooks.fix_error_editor(e.filename,
713 self.hooks.fix_error_editor(e.filename,
715 int0(e.lineno),int0(e.offset),e.msg)
714 int0(e.lineno),int0(e.offset),e.msg)
716 except TryNext:
715 except TryNext:
717 warn('Could not open editor')
716 warn('Could not open editor')
718 return False
717 return False
719 return True
718 return True
720
719
721 #-------------------------------------------------------------------------
720 #-------------------------------------------------------------------------
722 # Things related to exiting
721 # Things related to exiting
723 #-------------------------------------------------------------------------
722 #-------------------------------------------------------------------------
724
723
725 def ask_exit(self):
724 def ask_exit(self):
726 """ Ask the shell to exit. Can be overiden and used as a callback. """
725 """ Ask the shell to exit. Can be overiden and used as a callback. """
727 self.exit_now = True
726 self.exit_now = True
728
727
729 def exit(self):
728 def exit(self):
730 """Handle interactive exit.
729 """Handle interactive exit.
731
730
732 This method calls the ask_exit callback."""
731 This method calls the ask_exit callback."""
733 if self.confirm_exit:
732 if self.confirm_exit:
734 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'):
733 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'):
735 self.ask_exit()
734 self.ask_exit()
736 else:
735 else:
737 self.ask_exit()
736 self.ask_exit()
738
737
739 #-------------------------------------------------------------------------
738 #-------------------------------------------------------------------------
740 # Things related to magics
739 # Things related to magics
741 #-------------------------------------------------------------------------
740 #-------------------------------------------------------------------------
742
741
743 def init_magics(self):
742 def init_magics(self):
744 super(TerminalInteractiveShell, self).init_magics()
743 super(TerminalInteractiveShell, self).init_magics()
745 self.register_magics(TerminalMagics)
744 self.register_magics(TerminalMagics)
746
745
747 def showindentationerror(self):
746 def showindentationerror(self):
748 super(TerminalInteractiveShell, self).showindentationerror()
747 super(TerminalInteractiveShell, self).showindentationerror()
749 if not self.using_paste_magics:
748 if not self.using_paste_magics:
750 print("If you want to paste code into IPython, try the "
749 print("If you want to paste code into IPython, try the "
751 "%paste and %cpaste magic functions.")
750 "%paste and %cpaste magic functions.")
752
751
753
752
754 InteractiveShellABC.register(TerminalInteractiveShell)
753 InteractiveShellABC.register(TerminalInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now