##// END OF EJS Templates
Fix for starting the Qt console
Thomas Kluyver -
Show More
@@ -1,713 +1,721 b''
1 """Analysis of text input into executable blocks.
1 """Analysis of text input into executable blocks.
2
2
3 The main class in this module, :class:`InputSplitter`, is designed to break
3 The main class in this module, :class:`InputSplitter`, is designed to break
4 input from either interactive, line-by-line environments or block-based ones,
4 input from either interactive, line-by-line environments or block-based ones,
5 into standalone blocks that can be executed by Python as 'single' statements
5 into standalone blocks that can be executed by Python as 'single' statements
6 (thus triggering sys.displayhook).
6 (thus triggering sys.displayhook).
7
7
8 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
8 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
9 with full support for the extended IPython syntax (magics, system calls, etc).
9 with full support for the extended IPython syntax (magics, system calls, etc).
10
10
11 For more details, see the class docstring below.
11 For more details, see the class docstring below.
12
12
13 Syntax Transformations
13 Syntax Transformations
14 ----------------------
14 ----------------------
15
15
16 One of the main jobs of the code in this file is to apply all syntax
16 One of the main jobs of the code in this file is to apply all syntax
17 transformations that make up 'the IPython language', i.e. magics, shell
17 transformations that make up 'the IPython language', i.e. magics, shell
18 escapes, etc. All transformations should be implemented as *fully stateless*
18 escapes, etc. All transformations should be implemented as *fully stateless*
19 entities, that simply take one line as their input and return a line.
19 entities, that simply take one line as their input and return a line.
20 Internally for implementation purposes they may be a normal function or a
20 Internally for implementation purposes they may be a normal function or a
21 callable object, but the only input they receive will be a single line and they
21 callable object, but the only input they receive will be a single line and they
22 should only return a line, without holding any data-dependent state between
22 should only return a line, without holding any data-dependent state between
23 calls.
23 calls.
24
24
25 As an example, the EscapedTransformer is a class so we can more clearly group
25 As an example, the EscapedTransformer is a class so we can more clearly group
26 together the functionality of dispatching to individual functions based on the
26 together the functionality of dispatching to individual functions based on the
27 starting escape character, but the only method for public use is its call
27 starting escape character, but the only method for public use is its call
28 method.
28 method.
29
29
30
30
31 ToDo
31 ToDo
32 ----
32 ----
33
33
34 - Should we make push() actually raise an exception once push_accepts_more()
34 - Should we make push() actually raise an exception once push_accepts_more()
35 returns False?
35 returns False?
36
36
37 - Naming cleanups. The tr_* names aren't the most elegant, though now they are
37 - Naming cleanups. The tr_* names aren't the most elegant, though now they are
38 at least just attributes of a class so not really very exposed.
38 at least just attributes of a class so not really very exposed.
39
39
40 - Think about the best way to support dynamic things: automagic, autocall,
40 - Think about the best way to support dynamic things: automagic, autocall,
41 macros, etc.
41 macros, etc.
42
42
43 - Think of a better heuristic for the application of the transforms in
43 - Think of a better heuristic for the application of the transforms in
44 IPythonInputSplitter.push() than looking at the buffer ending in ':'. Idea:
44 IPythonInputSplitter.push() than looking at the buffer ending in ':'. Idea:
45 track indentation change events (indent, dedent, nothing) and apply them only
45 track indentation change events (indent, dedent, nothing) and apply them only
46 if the indentation went up, but not otherwise.
46 if the indentation went up, but not otherwise.
47
47
48 - Think of the cleanest way for supporting user-specified transformations (the
48 - Think of the cleanest way for supporting user-specified transformations (the
49 user prefilters we had before).
49 user prefilters we had before).
50
50
51 Authors
51 Authors
52 -------
52 -------
53
53
54 * Fernando Perez
54 * Fernando Perez
55 * Brian Granger
55 * Brian Granger
56 """
56 """
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Copyright (C) 2010 The IPython Development Team
58 # Copyright (C) 2010 The IPython Development Team
59 #
59 #
60 # Distributed under the terms of the BSD License. The full license is in
60 # Distributed under the terms of the BSD License. The full license is in
61 # the file COPYING, distributed as part of this software.
61 # the file COPYING, distributed as part of this software.
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Imports
65 # Imports
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67 # stdlib
67 # stdlib
68 import ast
68 import ast
69 import codeop
69 import codeop
70 import re
70 import re
71 import sys
71 import sys
72
72
73 # IPython modules
73 # IPython modules
74 from IPython.core.splitinput import split_user_input, LineInfo
74 from IPython.core.splitinput import split_user_input, LineInfo
75 from IPython.utils.py3compat import cast_unicode
75 from IPython.utils.py3compat import cast_unicode
76 from IPython.core.inputtransformer import (leading_indent,
76 from IPython.core.inputtransformer import (leading_indent,
77 classic_prompt,
77 classic_prompt,
78 ipy_prompt,
78 ipy_prompt,
79 cellmagic,
79 cellmagic,
80 assemble_logical_lines,
80 assemble_logical_lines,
81 help_end,
81 help_end,
82 escaped_commands,
82 escaped_commands,
83 assign_from_magic,
83 assign_from_magic,
84 assign_from_system,
84 assign_from_system,
85 assemble_python_lines,
85 assemble_python_lines,
86 )
86 )
87
87
88 # Temporary!
88 # Temporary!
89 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
89 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
90 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
90 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
91 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
91 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
92
92
93 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
94 # Utilities
94 # Utilities
95 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
96
96
97 # FIXME: These are general-purpose utilities that later can be moved to the
97 # FIXME: These are general-purpose utilities that later can be moved to the
98 # general ward. Kept here for now because we're being very strict about test
98 # general ward. Kept here for now because we're being very strict about test
99 # coverage with this code, and this lets us ensure that we keep 100% coverage
99 # coverage with this code, and this lets us ensure that we keep 100% coverage
100 # while developing.
100 # while developing.
101
101
102 # compiled regexps for autoindent management
102 # compiled regexps for autoindent management
103 dedent_re = re.compile('|'.join([
103 dedent_re = re.compile('|'.join([
104 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
104 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
105 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
105 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
106 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
106 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
107 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
107 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
108 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
108 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
109 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
109 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
110 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
110 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
111 ]))
111 ]))
112 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
112 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
113
113
114 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
114 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
115 # before pure comments
115 # before pure comments
116 comment_line_re = re.compile('^\s*\#')
116 comment_line_re = re.compile('^\s*\#')
117
117
118
118
119 def num_ini_spaces(s):
119 def num_ini_spaces(s):
120 """Return the number of initial spaces in a string.
120 """Return the number of initial spaces in a string.
121
121
122 Note that tabs are counted as a single space. For now, we do *not* support
122 Note that tabs are counted as a single space. For now, we do *not* support
123 mixing of tabs and spaces in the user's input.
123 mixing of tabs and spaces in the user's input.
124
124
125 Parameters
125 Parameters
126 ----------
126 ----------
127 s : string
127 s : string
128
128
129 Returns
129 Returns
130 -------
130 -------
131 n : int
131 n : int
132 """
132 """
133
133
134 ini_spaces = ini_spaces_re.match(s)
134 ini_spaces = ini_spaces_re.match(s)
135 if ini_spaces:
135 if ini_spaces:
136 return ini_spaces.end()
136 return ini_spaces.end()
137 else:
137 else:
138 return 0
138 return 0
139
139
140 def last_blank(src):
140 def last_blank(src):
141 """Determine if the input source ends in a blank.
141 """Determine if the input source ends in a blank.
142
142
143 A blank is either a newline or a line consisting of whitespace.
143 A blank is either a newline or a line consisting of whitespace.
144
144
145 Parameters
145 Parameters
146 ----------
146 ----------
147 src : string
147 src : string
148 A single or multiline string.
148 A single or multiline string.
149 """
149 """
150 if not src: return False
150 if not src: return False
151 ll = src.splitlines()[-1]
151 ll = src.splitlines()[-1]
152 return (ll == '') or ll.isspace()
152 return (ll == '') or ll.isspace()
153
153
154
154
155 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
155 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
156 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
156 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
157
157
158 def last_two_blanks(src):
158 def last_two_blanks(src):
159 """Determine if the input source ends in two blanks.
159 """Determine if the input source ends in two blanks.
160
160
161 A blank is either a newline or a line consisting of whitespace.
161 A blank is either a newline or a line consisting of whitespace.
162
162
163 Parameters
163 Parameters
164 ----------
164 ----------
165 src : string
165 src : string
166 A single or multiline string.
166 A single or multiline string.
167 """
167 """
168 if not src: return False
168 if not src: return False
169 # The logic here is tricky: I couldn't get a regexp to work and pass all
169 # The logic here is tricky: I couldn't get a regexp to work and pass all
170 # the tests, so I took a different approach: split the source by lines,
170 # the tests, so I took a different approach: split the source by lines,
171 # grab the last two and prepend '###\n' as a stand-in for whatever was in
171 # grab the last two and prepend '###\n' as a stand-in for whatever was in
172 # the body before the last two lines. Then, with that structure, it's
172 # the body before the last two lines. Then, with that structure, it's
173 # possible to analyze with two regexps. Not the most elegant solution, but
173 # possible to analyze with two regexps. Not the most elegant solution, but
174 # it works. If anyone tries to change this logic, make sure to validate
174 # it works. If anyone tries to change this logic, make sure to validate
175 # the whole test suite first!
175 # the whole test suite first!
176 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
176 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
177 return (bool(last_two_blanks_re.match(new_src)) or
177 return (bool(last_two_blanks_re.match(new_src)) or
178 bool(last_two_blanks_re2.match(new_src)) )
178 bool(last_two_blanks_re2.match(new_src)) )
179
179
180
180
181 def remove_comments(src):
181 def remove_comments(src):
182 """Remove all comments from input source.
182 """Remove all comments from input source.
183
183
184 Note: comments are NOT recognized inside of strings!
184 Note: comments are NOT recognized inside of strings!
185
185
186 Parameters
186 Parameters
187 ----------
187 ----------
188 src : string
188 src : string
189 A single or multiline input string.
189 A single or multiline input string.
190
190
191 Returns
191 Returns
192 -------
192 -------
193 String with all Python comments removed.
193 String with all Python comments removed.
194 """
194 """
195
195
196 return re.sub('#.*', '', src)
196 return re.sub('#.*', '', src)
197
197
198
198
199 def get_input_encoding():
199 def get_input_encoding():
200 """Return the default standard input encoding.
200 """Return the default standard input encoding.
201
201
202 If sys.stdin has no encoding, 'ascii' is returned."""
202 If sys.stdin has no encoding, 'ascii' is returned."""
203 # There are strange environments for which sys.stdin.encoding is None. We
203 # There are strange environments for which sys.stdin.encoding is None. We
204 # ensure that a valid encoding is returned.
204 # ensure that a valid encoding is returned.
205 encoding = getattr(sys.stdin, 'encoding', None)
205 encoding = getattr(sys.stdin, 'encoding', None)
206 if encoding is None:
206 if encoding is None:
207 encoding = 'ascii'
207 encoding = 'ascii'
208 return encoding
208 return encoding
209
209
210 #-----------------------------------------------------------------------------
210 #-----------------------------------------------------------------------------
211 # Classes and functions for normal Python syntax handling
211 # Classes and functions for normal Python syntax handling
212 #-----------------------------------------------------------------------------
212 #-----------------------------------------------------------------------------
213
213
214 class InputSplitter(object):
214 class InputSplitter(object):
215 """An object that can accumulate lines of Python source before execution.
215 """An object that can accumulate lines of Python source before execution.
216
216
217 This object is designed to be fed python source line-by-line, using
217 This object is designed to be fed python source line-by-line, using
218 :meth:`push`. It will return on each push whether the currently pushed
218 :meth:`push`. It will return on each push whether the currently pushed
219 code could be executed already. In addition, it provides a method called
219 code could be executed already. In addition, it provides a method called
220 :meth:`push_accepts_more` that can be used to query whether more input
220 :meth:`push_accepts_more` that can be used to query whether more input
221 can be pushed into a single interactive block.
221 can be pushed into a single interactive block.
222
222
223 This is a simple example of how an interactive terminal-based client can use
223 This is a simple example of how an interactive terminal-based client can use
224 this tool::
224 this tool::
225
225
226 isp = InputSplitter()
226 isp = InputSplitter()
227 while isp.push_accepts_more():
227 while isp.push_accepts_more():
228 indent = ' '*isp.indent_spaces
228 indent = ' '*isp.indent_spaces
229 prompt = '>>> ' + indent
229 prompt = '>>> ' + indent
230 line = indent + raw_input(prompt)
230 line = indent + raw_input(prompt)
231 isp.push(line)
231 isp.push(line)
232 print 'Input source was:\n', isp.source_reset(),
232 print 'Input source was:\n', isp.source_reset(),
233 """
233 """
234 # Number of spaces of indentation computed from input that has been pushed
234 # Number of spaces of indentation computed from input that has been pushed
235 # so far. This is the attributes callers should query to get the current
235 # so far. This is the attributes callers should query to get the current
236 # indentation level, in order to provide auto-indent facilities.
236 # indentation level, in order to provide auto-indent facilities.
237 indent_spaces = 0
237 indent_spaces = 0
238 # String, indicating the default input encoding. It is computed by default
238 # String, indicating the default input encoding. It is computed by default
239 # at initialization time via get_input_encoding(), but it can be reset by a
239 # at initialization time via get_input_encoding(), but it can be reset by a
240 # client with specific knowledge of the encoding.
240 # client with specific knowledge of the encoding.
241 encoding = ''
241 encoding = ''
242 # String where the current full source input is stored, properly encoded.
242 # String where the current full source input is stored, properly encoded.
243 # Reading this attribute is the normal way of querying the currently pushed
243 # Reading this attribute is the normal way of querying the currently pushed
244 # source code, that has been properly encoded.
244 # source code, that has been properly encoded.
245 source = ''
245 source = ''
246 # Code object corresponding to the current source. It is automatically
246 # Code object corresponding to the current source. It is automatically
247 # synced to the source, so it can be queried at any time to obtain the code
247 # synced to the source, so it can be queried at any time to obtain the code
248 # object; it will be None if the source doesn't compile to valid Python.
248 # object; it will be None if the source doesn't compile to valid Python.
249 code = None
249 code = None
250 # Input mode
250 # Input mode
251 input_mode = 'line'
251 input_mode = 'line'
252
252
253 # Private attributes
253 # Private attributes
254
254
255 # List with lines of input accumulated so far
255 # List with lines of input accumulated so far
256 _buffer = None
256 _buffer = None
257 # Command compiler
257 # Command compiler
258 _compile = None
258 _compile = None
259 # Mark when input has changed indentation all the way back to flush-left
259 # Mark when input has changed indentation all the way back to flush-left
260 _full_dedent = False
260 _full_dedent = False
261 # Boolean indicating whether the current block is complete
261 # Boolean indicating whether the current block is complete
262 _is_complete = None
262 _is_complete = None
263
263
264 def __init__(self, input_mode=None):
264 def __init__(self, input_mode=None):
265 """Create a new InputSplitter instance.
265 """Create a new InputSplitter instance.
266
266
267 Parameters
267 Parameters
268 ----------
268 ----------
269 input_mode : str
269 input_mode : str
270
270
271 One of ['line', 'cell']; default is 'line'.
271 One of ['line', 'cell']; default is 'line'.
272
272
273 The input_mode parameter controls how new inputs are used when fed via
273 The input_mode parameter controls how new inputs are used when fed via
274 the :meth:`push` method:
274 the :meth:`push` method:
275
275
276 - 'line': meant for line-oriented clients, inputs are appended one at a
276 - 'line': meant for line-oriented clients, inputs are appended one at a
277 time to the internal buffer and the whole buffer is compiled.
277 time to the internal buffer and the whole buffer is compiled.
278
278
279 - 'cell': meant for clients that can edit multi-line 'cells' of text at
279 - 'cell': meant for clients that can edit multi-line 'cells' of text at
280 a time. A cell can contain one or more blocks that can be compile in
280 a time. A cell can contain one or more blocks that can be compile in
281 'single' mode by Python. In this mode, each new input new input
281 'single' mode by Python. In this mode, each new input new input
282 completely replaces all prior inputs. Cell mode is thus equivalent
282 completely replaces all prior inputs. Cell mode is thus equivalent
283 to prepending a full reset() to every push() call.
283 to prepending a full reset() to every push() call.
284 """
284 """
285 self._buffer = []
285 self._buffer = []
286 self._compile = codeop.CommandCompiler()
286 self._compile = codeop.CommandCompiler()
287 self.encoding = get_input_encoding()
287 self.encoding = get_input_encoding()
288 self.input_mode = InputSplitter.input_mode if input_mode is None \
288 self.input_mode = InputSplitter.input_mode if input_mode is None \
289 else input_mode
289 else input_mode
290
290
291 def reset(self):
291 def reset(self):
292 """Reset the input buffer and associated state."""
292 """Reset the input buffer and associated state."""
293 self.indent_spaces = 0
293 self.indent_spaces = 0
294 self._buffer[:] = []
294 self._buffer[:] = []
295 self.source = ''
295 self.source = ''
296 self.code = None
296 self.code = None
297 self._is_complete = False
297 self._is_complete = False
298 self._full_dedent = False
298 self._full_dedent = False
299
299
300 def source_reset(self):
300 def source_reset(self):
301 """Return the input source and perform a full reset.
301 """Return the input source and perform a full reset.
302 """
302 """
303 out = self.source
303 out = self.source
304 self.reset()
304 self.reset()
305 return out
305 return out
306
306
307 def push(self, lines):
307 def push(self, lines):
308 """Push one or more lines of input.
308 """Push one or more lines of input.
309
309
310 This stores the given lines and returns a status code indicating
310 This stores the given lines and returns a status code indicating
311 whether the code forms a complete Python block or not.
311 whether the code forms a complete Python block or not.
312
312
313 Any exceptions generated in compilation are swallowed, but if an
313 Any exceptions generated in compilation are swallowed, but if an
314 exception was produced, the method returns True.
314 exception was produced, the method returns True.
315
315
316 Parameters
316 Parameters
317 ----------
317 ----------
318 lines : string
318 lines : string
319 One or more lines of Python input.
319 One or more lines of Python input.
320
320
321 Returns
321 Returns
322 -------
322 -------
323 is_complete : boolean
323 is_complete : boolean
324 True if the current input source (the result of the current input
324 True if the current input source (the result of the current input
325 plus prior inputs) forms a complete Python execution block. Note that
325 plus prior inputs) forms a complete Python execution block. Note that
326 this value is also stored as a private attribute (``_is_complete``), so it
326 this value is also stored as a private attribute (``_is_complete``), so it
327 can be queried at any time.
327 can be queried at any time.
328 """
328 """
329 if self.input_mode == 'cell':
329 if self.input_mode == 'cell':
330 self.reset()
330 self.reset()
331
331
332 self._store(lines)
332 self._store(lines)
333 source = self.source
333 source = self.source
334
334
335 # Before calling _compile(), reset the code object to None so that if an
335 # Before calling _compile(), reset the code object to None so that if an
336 # exception is raised in compilation, we don't mislead by having
336 # exception is raised in compilation, we don't mislead by having
337 # inconsistent code/source attributes.
337 # inconsistent code/source attributes.
338 self.code, self._is_complete = None, None
338 self.code, self._is_complete = None, None
339
339
340 # Honor termination lines properly
340 # Honor termination lines properly
341 if source.endswith('\\\n'):
341 if source.endswith('\\\n'):
342 return False
342 return False
343
343
344 self._update_indent(lines)
344 self._update_indent(lines)
345 try:
345 try:
346 self.code = self._compile(source, symbol="exec")
346 self.code = self._compile(source, symbol="exec")
347 # Invalid syntax can produce any of a number of different errors from
347 # Invalid syntax can produce any of a number of different errors from
348 # inside the compiler, so we have to catch them all. Syntax errors
348 # inside the compiler, so we have to catch them all. Syntax errors
349 # immediately produce a 'ready' block, so the invalid Python can be
349 # immediately produce a 'ready' block, so the invalid Python can be
350 # sent to the kernel for evaluation with possible ipython
350 # sent to the kernel for evaluation with possible ipython
351 # special-syntax conversion.
351 # special-syntax conversion.
352 except (SyntaxError, OverflowError, ValueError, TypeError,
352 except (SyntaxError, OverflowError, ValueError, TypeError,
353 MemoryError):
353 MemoryError):
354 self._is_complete = True
354 self._is_complete = True
355 else:
355 else:
356 # Compilation didn't produce any exceptions (though it may not have
356 # Compilation didn't produce any exceptions (though it may not have
357 # given a complete code object)
357 # given a complete code object)
358 self._is_complete = self.code is not None
358 self._is_complete = self.code is not None
359
359
360 return self._is_complete
360 return self._is_complete
361
361
362 def push_accepts_more(self):
362 def push_accepts_more(self):
363 """Return whether a block of interactive input can accept more input.
363 """Return whether a block of interactive input can accept more input.
364
364
365 This method is meant to be used by line-oriented frontends, who need to
365 This method is meant to be used by line-oriented frontends, who need to
366 guess whether a block is complete or not based solely on prior and
366 guess whether a block is complete or not based solely on prior and
367 current input lines. The InputSplitter considers it has a complete
367 current input lines. The InputSplitter considers it has a complete
368 interactive block and will not accept more input only when either a
368 interactive block and will not accept more input only when either a
369 SyntaxError is raised, or *all* of the following are true:
369 SyntaxError is raised, or *all* of the following are true:
370
370
371 1. The input compiles to a complete statement.
371 1. The input compiles to a complete statement.
372
372
373 2. The indentation level is flush-left (because if we are indented,
373 2. The indentation level is flush-left (because if we are indented,
374 like inside a function definition or for loop, we need to keep
374 like inside a function definition or for loop, we need to keep
375 reading new input).
375 reading new input).
376
376
377 3. There is one extra line consisting only of whitespace.
377 3. There is one extra line consisting only of whitespace.
378
378
379 Because of condition #3, this method should be used only by
379 Because of condition #3, this method should be used only by
380 *line-oriented* frontends, since it means that intermediate blank lines
380 *line-oriented* frontends, since it means that intermediate blank lines
381 are not allowed in function definitions (or any other indented block).
381 are not allowed in function definitions (or any other indented block).
382
382
383 If the current input produces a syntax error, this method immediately
383 If the current input produces a syntax error, this method immediately
384 returns False but does *not* raise the syntax error exception, as
384 returns False but does *not* raise the syntax error exception, as
385 typically clients will want to send invalid syntax to an execution
385 typically clients will want to send invalid syntax to an execution
386 backend which might convert the invalid syntax into valid Python via
386 backend which might convert the invalid syntax into valid Python via
387 one of the dynamic IPython mechanisms.
387 one of the dynamic IPython mechanisms.
388 """
388 """
389
389
390 # With incomplete input, unconditionally accept more
390 # With incomplete input, unconditionally accept more
391 if not self._is_complete:
391 if not self._is_complete:
392 return True
392 return True
393
393
394 # If we already have complete input and we're flush left, the answer
394 # If we already have complete input and we're flush left, the answer
395 # depends. In line mode, if there hasn't been any indentation,
395 # depends. In line mode, if there hasn't been any indentation,
396 # that's it. If we've come back from some indentation, we need
396 # that's it. If we've come back from some indentation, we need
397 # the blank final line to finish.
397 # the blank final line to finish.
398 # In cell mode, we need to check how many blocks the input so far
398 # In cell mode, we need to check how many blocks the input so far
399 # compiles into, because if there's already more than one full
399 # compiles into, because if there's already more than one full
400 # independent block of input, then the client has entered full
400 # independent block of input, then the client has entered full
401 # 'cell' mode and is feeding lines that each is complete. In this
401 # 'cell' mode and is feeding lines that each is complete. In this
402 # case we should then keep accepting. The Qt terminal-like console
402 # case we should then keep accepting. The Qt terminal-like console
403 # does precisely this, to provide the convenience of terminal-like
403 # does precisely this, to provide the convenience of terminal-like
404 # input of single expressions, but allowing the user (with a
404 # input of single expressions, but allowing the user (with a
405 # separate keystroke) to switch to 'cell' mode and type multiple
405 # separate keystroke) to switch to 'cell' mode and type multiple
406 # expressions in one shot.
406 # expressions in one shot.
407 if self.indent_spaces==0:
407 if self.indent_spaces==0:
408 if self.input_mode=='line':
408 if self.input_mode=='line':
409 if not self._full_dedent:
409 if not self._full_dedent:
410 return False
410 return False
411 else:
411 else:
412 try:
412 try:
413 code_ast = ast.parse(u''.join(self._buffer))
413 code_ast = ast.parse(u''.join(self._buffer))
414 except Exception:
414 except Exception:
415 return False
415 return False
416 else:
416 else:
417 if len(code_ast.body) == 1:
417 if len(code_ast.body) == 1:
418 return False
418 return False
419
419
420 # When input is complete, then termination is marked by an extra blank
420 # When input is complete, then termination is marked by an extra blank
421 # line at the end.
421 # line at the end.
422 last_line = self.source.splitlines()[-1]
422 last_line = self.source.splitlines()[-1]
423 return bool(last_line and not last_line.isspace())
423 return bool(last_line and not last_line.isspace())
424
424
425 #------------------------------------------------------------------------
425 #------------------------------------------------------------------------
426 # Private interface
426 # Private interface
427 #------------------------------------------------------------------------
427 #------------------------------------------------------------------------
428
428
429 def _find_indent(self, line):
429 def _find_indent(self, line):
430 """Compute the new indentation level for a single line.
430 """Compute the new indentation level for a single line.
431
431
432 Parameters
432 Parameters
433 ----------
433 ----------
434 line : str
434 line : str
435 A single new line of non-whitespace, non-comment Python input.
435 A single new line of non-whitespace, non-comment Python input.
436
436
437 Returns
437 Returns
438 -------
438 -------
439 indent_spaces : int
439 indent_spaces : int
440 New value for the indent level (it may be equal to self.indent_spaces
440 New value for the indent level (it may be equal to self.indent_spaces
441 if indentation doesn't change.
441 if indentation doesn't change.
442
442
443 full_dedent : boolean
443 full_dedent : boolean
444 Whether the new line causes a full flush-left dedent.
444 Whether the new line causes a full flush-left dedent.
445 """
445 """
446 indent_spaces = self.indent_spaces
446 indent_spaces = self.indent_spaces
447 full_dedent = self._full_dedent
447 full_dedent = self._full_dedent
448
448
449 inisp = num_ini_spaces(line)
449 inisp = num_ini_spaces(line)
450 if inisp < indent_spaces:
450 if inisp < indent_spaces:
451 indent_spaces = inisp
451 indent_spaces = inisp
452 if indent_spaces <= 0:
452 if indent_spaces <= 0:
453 #print 'Full dedent in text',self.source # dbg
453 #print 'Full dedent in text',self.source # dbg
454 full_dedent = True
454 full_dedent = True
455
455
456 if line.rstrip()[-1] == ':':
456 if line.rstrip()[-1] == ':':
457 indent_spaces += 4
457 indent_spaces += 4
458 elif dedent_re.match(line):
458 elif dedent_re.match(line):
459 indent_spaces -= 4
459 indent_spaces -= 4
460 if indent_spaces <= 0:
460 if indent_spaces <= 0:
461 full_dedent = True
461 full_dedent = True
462
462
463 # Safety
463 # Safety
464 if indent_spaces < 0:
464 if indent_spaces < 0:
465 indent_spaces = 0
465 indent_spaces = 0
466 #print 'safety' # dbg
466 #print 'safety' # dbg
467
467
468 return indent_spaces, full_dedent
468 return indent_spaces, full_dedent
469
469
470 def _update_indent(self, lines):
470 def _update_indent(self, lines):
471 for line in remove_comments(lines).splitlines():
471 for line in remove_comments(lines).splitlines():
472 if line and not line.isspace():
472 if line and not line.isspace():
473 self.indent_spaces, self._full_dedent = self._find_indent(line)
473 self.indent_spaces, self._full_dedent = self._find_indent(line)
474
474
475 def _store(self, lines, buffer=None, store='source'):
475 def _store(self, lines, buffer=None, store='source'):
476 """Store one or more lines of input.
476 """Store one or more lines of input.
477
477
478 If input lines are not newline-terminated, a newline is automatically
478 If input lines are not newline-terminated, a newline is automatically
479 appended."""
479 appended."""
480
480
481 if buffer is None:
481 if buffer is None:
482 buffer = self._buffer
482 buffer = self._buffer
483
483
484 if lines.endswith('\n'):
484 if lines.endswith('\n'):
485 buffer.append(lines)
485 buffer.append(lines)
486 else:
486 else:
487 buffer.append(lines+'\n')
487 buffer.append(lines+'\n')
488 setattr(self, store, self._set_source(buffer))
488 setattr(self, store, self._set_source(buffer))
489
489
490 def _set_source(self, buffer):
490 def _set_source(self, buffer):
491 return u''.join(buffer)
491 return u''.join(buffer)
492
492
493
493
494 class IPythonInputSplitter(InputSplitter):
494 class IPythonInputSplitter(InputSplitter):
495 """An input splitter that recognizes all of IPython's special syntax."""
495 """An input splitter that recognizes all of IPython's special syntax."""
496
496
497 # String with raw, untransformed input.
497 # String with raw, untransformed input.
498 source_raw = ''
498 source_raw = ''
499
499
500 # Flag to track when a transformer has stored input that it hasn't given
500 # Flag to track when a transformer has stored input that it hasn't given
501 # back yet.
501 # back yet.
502 transformer_accumulating = False
502 transformer_accumulating = False
503
503
504 # Flag to track when assemble_python_lines has stored input that it hasn't
504 # Flag to track when assemble_python_lines has stored input that it hasn't
505 # given back yet.
505 # given back yet.
506 within_python_line = False
506 within_python_line = False
507
507
508 # Private attributes
508 # Private attributes
509
509
510 # List with lines of raw input accumulated so far.
510 # List with lines of raw input accumulated so far.
511 _buffer_raw = None
511 _buffer_raw = None
512
512
513 def __init__(self, input_mode=None, physical_line_transforms=None,
513 def __init__(self, input_mode=None, physical_line_transforms=None,
514 logical_line_transforms=None, python_line_transforms=None):
514 logical_line_transforms=None, python_line_transforms=None):
515 super(IPythonInputSplitter, self).__init__(input_mode)
515 super(IPythonInputSplitter, self).__init__(input_mode)
516 self._buffer_raw = []
516 self._buffer_raw = []
517 self._validate = True
517 self._validate = True
518
518
519 self.physical_line_transforms = physical_line_transforms or \
519 if physical_line_transforms is not None:
520 [leading_indent(),
520 self.physical_line_transforms = physical_line_transforms
521 classic_prompt(),
521 else:
522 ipy_prompt(),
522 self.physical_line_transforms = [leading_indent(),
523 ]
523 classic_prompt(),
524 ipy_prompt(),
525 ]
524
526
525 self.assemble_logical_lines = assemble_logical_lines()
527 self.assemble_logical_lines = assemble_logical_lines()
526 self.logical_line_transforms = logical_line_transforms or \
528 if logical_line_transforms is not None:
527 [cellmagic(),
529 self.logical_line_transforms = logical_line_transforms
528 help_end(),
530 else:
529 escaped_commands(),
531 self.logical_line_transforms = [cellmagic(),
530 assign_from_magic(),
532 help_end(),
531 assign_from_system(),
533 escaped_commands(),
532 ]
534 assign_from_magic(),
535 assign_from_system(),
536 ]
533
537
534 self.assemble_python_lines = assemble_python_lines()
538 self.assemble_python_lines = assemble_python_lines()
535 self.python_line_transforms = python_line_transforms or []
539 if python_line_transforms is not None:
540 self.python_line_transforms = python_line_transforms
541 else:
542 # We don't use any of these at present
543 self.python_line_transforms = []
536
544
537 @property
545 @property
538 def transforms(self):
546 def transforms(self):
539 "Quick access to all transformers."
547 "Quick access to all transformers."
540 return self.physical_line_transforms + \
548 return self.physical_line_transforms + \
541 [self.assemble_logical_lines] + self.logical_line_transforms + \
549 [self.assemble_logical_lines] + self.logical_line_transforms + \
542 [self.assemble_python_lines] + self.python_line_transforms
550 [self.assemble_python_lines] + self.python_line_transforms
543
551
544 @property
552 @property
545 def transforms_in_use(self):
553 def transforms_in_use(self):
546 """Transformers, excluding logical line transformers if we're in a
554 """Transformers, excluding logical line transformers if we're in a
547 Python line."""
555 Python line."""
548 t = self.physical_line_transforms[:]
556 t = self.physical_line_transforms[:]
549 if not self.within_python_line:
557 if not self.within_python_line:
550 t += [self.assemble_logical_lines] + self.logical_line_transforms
558 t += [self.assemble_logical_lines] + self.logical_line_transforms
551 return t + [self.assemble_python_lines] + self.python_line_transforms
559 return t + [self.assemble_python_lines] + self.python_line_transforms
552
560
553 def reset(self):
561 def reset(self):
554 """Reset the input buffer and associated state."""
562 """Reset the input buffer and associated state."""
555 super(IPythonInputSplitter, self).reset()
563 super(IPythonInputSplitter, self).reset()
556 self._buffer_raw[:] = []
564 self._buffer_raw[:] = []
557 self.source_raw = ''
565 self.source_raw = ''
558 self.transformer_accumulating = False
566 self.transformer_accumulating = False
559 self.within_python_line = False
567 self.within_python_line = False
560 for t in self.transforms:
568 for t in self.transforms:
561 t.reset()
569 t.reset()
562
570
563 def flush_transformers(self):
571 def flush_transformers(self):
564 def _flush(transform, out):
572 def _flush(transform, out):
565 if out is not None:
573 if out is not None:
566 tmp = transform.push(out)
574 tmp = transform.push(out)
567 return tmp or transform.reset() or None
575 return tmp or transform.reset() or None
568 else:
576 else:
569 return transform.reset() or None
577 return transform.reset() or None
570
578
571 out = None
579 out = None
572 for t in self.transforms_in_use:
580 for t in self.transforms_in_use:
573 out = _flush(t, out)
581 out = _flush(t, out)
574
582
575 if out is not None:
583 if out is not None:
576 self._store(out)
584 self._store(out)
577
585
578 def source_raw_reset(self):
586 def source_raw_reset(self):
579 """Return input and raw source and perform a full reset.
587 """Return input and raw source and perform a full reset.
580 """
588 """
581 self.flush_transformers()
589 self.flush_transformers()
582 out = self.source
590 out = self.source
583 out_r = self.source_raw
591 out_r = self.source_raw
584 self.reset()
592 self.reset()
585 return out, out_r
593 return out, out_r
586
594
587 def source_reset(self):
595 def source_reset(self):
588 self.flush_transformers()
596 self.flush_transformers()
589 return super(IPythonInputSplitter, self).source_reset()
597 return super(IPythonInputSplitter, self).source_reset()
590
598
591 def push_accepts_more(self):
599 def push_accepts_more(self):
592 if self.transformer_accumulating:
600 if self.transformer_accumulating:
593 return True
601 return True
594 else:
602 else:
595 return super(IPythonInputSplitter, self).push_accepts_more()
603 return super(IPythonInputSplitter, self).push_accepts_more()
596
604
597 def transform_cell(self, cell):
605 def transform_cell(self, cell):
598 """Process and translate a cell of input.
606 """Process and translate a cell of input.
599 """
607 """
600 self.reset()
608 self.reset()
601 self.push(cell)
609 self.push(cell)
602 return self.source_reset()
610 return self.source_reset()
603
611
604 def push(self, lines):
612 def push(self, lines):
605 """Push one or more lines of IPython input.
613 """Push one or more lines of IPython input.
606
614
607 This stores the given lines and returns a status code indicating
615 This stores the given lines and returns a status code indicating
608 whether the code forms a complete Python block or not, after processing
616 whether the code forms a complete Python block or not, after processing
609 all input lines for special IPython syntax.
617 all input lines for special IPython syntax.
610
618
611 Any exceptions generated in compilation are swallowed, but if an
619 Any exceptions generated in compilation are swallowed, but if an
612 exception was produced, the method returns True.
620 exception was produced, the method returns True.
613
621
614 Parameters
622 Parameters
615 ----------
623 ----------
616 lines : string
624 lines : string
617 One or more lines of Python input.
625 One or more lines of Python input.
618
626
619 Returns
627 Returns
620 -------
628 -------
621 is_complete : boolean
629 is_complete : boolean
622 True if the current input source (the result of the current input
630 True if the current input source (the result of the current input
623 plus prior inputs) forms a complete Python execution block. Note that
631 plus prior inputs) forms a complete Python execution block. Note that
624 this value is also stored as a private attribute (_is_complete), so it
632 this value is also stored as a private attribute (_is_complete), so it
625 can be queried at any time.
633 can be queried at any time.
626 """
634 """
627
635
628 # We must ensure all input is pure unicode
636 # We must ensure all input is pure unicode
629 lines = cast_unicode(lines, self.encoding)
637 lines = cast_unicode(lines, self.encoding)
630
638
631 # ''.splitlines() --> [], but we need to push the empty line to transformers
639 # ''.splitlines() --> [], but we need to push the empty line to transformers
632 lines_list = lines.splitlines()
640 lines_list = lines.splitlines()
633 if not lines_list:
641 if not lines_list:
634 lines_list = ['']
642 lines_list = ['']
635
643
636 # Transform logic
644 # Transform logic
637 #
645 #
638 # We only apply the line transformers to the input if we have either no
646 # We only apply the line transformers to the input if we have either no
639 # input yet, or complete input, or if the last line of the buffer ends
647 # input yet, or complete input, or if the last line of the buffer ends
640 # with ':' (opening an indented block). This prevents the accidental
648 # with ':' (opening an indented block). This prevents the accidental
641 # transformation of escapes inside multiline expressions like
649 # transformation of escapes inside multiline expressions like
642 # triple-quoted strings or parenthesized expressions.
650 # triple-quoted strings or parenthesized expressions.
643 #
651 #
644 # The last heuristic, while ugly, ensures that the first line of an
652 # The last heuristic, while ugly, ensures that the first line of an
645 # indented block is correctly transformed.
653 # indented block is correctly transformed.
646 #
654 #
647 # FIXME: try to find a cleaner approach for this last bit.
655 # FIXME: try to find a cleaner approach for this last bit.
648
656
649 # If we were in 'block' mode, since we're going to pump the parent
657 # If we were in 'block' mode, since we're going to pump the parent
650 # class by hand line by line, we need to temporarily switch out to
658 # class by hand line by line, we need to temporarily switch out to
651 # 'line' mode, do a single manual reset and then feed the lines one
659 # 'line' mode, do a single manual reset and then feed the lines one
652 # by one. Note that this only matters if the input has more than one
660 # by one. Note that this only matters if the input has more than one
653 # line.
661 # line.
654 changed_input_mode = False
662 changed_input_mode = False
655
663
656 if self.input_mode == 'cell':
664 if self.input_mode == 'cell':
657 self.reset()
665 self.reset()
658 changed_input_mode = True
666 changed_input_mode = True
659 saved_input_mode = 'cell'
667 saved_input_mode = 'cell'
660 self.input_mode = 'line'
668 self.input_mode = 'line'
661
669
662 # Store raw source before applying any transformations to it. Note
670 # Store raw source before applying any transformations to it. Note
663 # that this must be done *after* the reset() call that would otherwise
671 # that this must be done *after* the reset() call that would otherwise
664 # flush the buffer.
672 # flush the buffer.
665 self._store(lines, self._buffer_raw, 'source_raw')
673 self._store(lines, self._buffer_raw, 'source_raw')
666
674
667 try:
675 try:
668 for line in lines_list:
676 for line in lines_list:
669 out = self.push_line(line)
677 out = self.push_line(line)
670 finally:
678 finally:
671 if changed_input_mode:
679 if changed_input_mode:
672 self.input_mode = saved_input_mode
680 self.input_mode = saved_input_mode
673
681
674 return out
682 return out
675
683
676 def push_line(self, line):
684 def push_line(self, line):
677 buf = self._buffer
685 buf = self._buffer
678
686
679 def _accumulating(dbg):
687 def _accumulating(dbg):
680 #print(dbg)
688 #print(dbg)
681 self.transformer_accumulating = True
689 self.transformer_accumulating = True
682 return False
690 return False
683
691
684 for transformer in self.physical_line_transforms:
692 for transformer in self.physical_line_transforms:
685 line = transformer.push(line)
693 line = transformer.push(line)
686 if line is None:
694 if line is None:
687 return _accumulating(transformer)
695 return _accumulating(transformer)
688
696
689 if not self.within_python_line:
697 if not self.within_python_line:
690 line = self.assemble_logical_lines.push(line)
698 line = self.assemble_logical_lines.push(line)
691 if line is None:
699 if line is None:
692 return _accumulating('acc logical line')
700 return _accumulating('acc logical line')
693
701
694 for transformer in self.logical_line_transforms:
702 for transformer in self.logical_line_transforms:
695 line = transformer.push(line)
703 line = transformer.push(line)
696 if line is None:
704 if line is None:
697 return _accumulating(transformer)
705 return _accumulating(transformer)
698
706
699 line = self.assemble_python_lines.push(line)
707 line = self.assemble_python_lines.push(line)
700 if line is None:
708 if line is None:
701 self.within_python_line = True
709 self.within_python_line = True
702 return _accumulating('acc python line')
710 return _accumulating('acc python line')
703 else:
711 else:
704 self.within_python_line = False
712 self.within_python_line = False
705
713
706 for transformer in self.python_line_transforms:
714 for transformer in self.python_line_transforms:
707 line = transformer.push(line)
715 line = transformer.push(line)
708 if line is None:
716 if line is None:
709 return _accumulating(transformer)
717 return _accumulating(transformer)
710
718
711 #print("transformers clear") #debug
719 #print("transformers clear") #debug
712 self.transformer_accumulating = False
720 self.transformer_accumulating = False
713 return super(IPythonInputSplitter, self).push(line)
721 return super(IPythonInputSplitter, self).push(line)
@@ -1,767 +1,770 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7 import uuid
7 import uuid
8
8
9 # System library imports
9 # System library imports
10 from pygments.lexers import PythonLexer
10 from pygments.lexers import PythonLexer
11 from IPython.external import qt
11 from IPython.external import qt
12 from IPython.external.qt import QtCore, QtGui
12 from IPython.external.qt import QtCore, QtGui
13
13
14 # Local imports
14 # Local imports
15 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
15 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
16 from IPython.core.inputtransformer import classic_prompt
16 from IPython.core.inputtransformer import classic_prompt
17 from IPython.core.oinspect import call_tip
17 from IPython.core.oinspect import call_tip
18 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
18 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
19 from IPython.utils.traitlets import Bool, Instance, Unicode
19 from IPython.utils.traitlets import Bool, Instance, Unicode
20 from bracket_matcher import BracketMatcher
20 from bracket_matcher import BracketMatcher
21 from call_tip_widget import CallTipWidget
21 from call_tip_widget import CallTipWidget
22 from completion_lexer import CompletionLexer
22 from completion_lexer import CompletionLexer
23 from history_console_widget import HistoryConsoleWidget
23 from history_console_widget import HistoryConsoleWidget
24 from pygments_highlighter import PygmentsHighlighter
24 from pygments_highlighter import PygmentsHighlighter
25
25
26
26
27 class FrontendHighlighter(PygmentsHighlighter):
27 class FrontendHighlighter(PygmentsHighlighter):
28 """ A PygmentsHighlighter that understands and ignores prompts.
28 """ A PygmentsHighlighter that understands and ignores prompts.
29 """
29 """
30
30
31 def __init__(self, frontend):
31 def __init__(self, frontend):
32 super(FrontendHighlighter, self).__init__(frontend._control.document())
32 super(FrontendHighlighter, self).__init__(frontend._control.document())
33 self._current_offset = 0
33 self._current_offset = 0
34 self._frontend = frontend
34 self._frontend = frontend
35 self.highlighting_on = False
35 self.highlighting_on = False
36
36
37 def highlightBlock(self, string):
37 def highlightBlock(self, string):
38 """ Highlight a block of text. Reimplemented to highlight selectively.
38 """ Highlight a block of text. Reimplemented to highlight selectively.
39 """
39 """
40 if not self.highlighting_on:
40 if not self.highlighting_on:
41 return
41 return
42
42
43 # The input to this function is a unicode string that may contain
43 # The input to this function is a unicode string that may contain
44 # paragraph break characters, non-breaking spaces, etc. Here we acquire
44 # paragraph break characters, non-breaking spaces, etc. Here we acquire
45 # the string as plain text so we can compare it.
45 # the string as plain text so we can compare it.
46 current_block = self.currentBlock()
46 current_block = self.currentBlock()
47 string = self._frontend._get_block_plain_text(current_block)
47 string = self._frontend._get_block_plain_text(current_block)
48
48
49 # Decide whether to check for the regular or continuation prompt.
49 # Decide whether to check for the regular or continuation prompt.
50 if current_block.contains(self._frontend._prompt_pos):
50 if current_block.contains(self._frontend._prompt_pos):
51 prompt = self._frontend._prompt
51 prompt = self._frontend._prompt
52 else:
52 else:
53 prompt = self._frontend._continuation_prompt
53 prompt = self._frontend._continuation_prompt
54
54
55 # Only highlight if we can identify a prompt, but make sure not to
55 # Only highlight if we can identify a prompt, but make sure not to
56 # highlight the prompt.
56 # highlight the prompt.
57 if string.startswith(prompt):
57 if string.startswith(prompt):
58 self._current_offset = len(prompt)
58 self._current_offset = len(prompt)
59 string = string[len(prompt):]
59 string = string[len(prompt):]
60 super(FrontendHighlighter, self).highlightBlock(string)
60 super(FrontendHighlighter, self).highlightBlock(string)
61
61
62 def rehighlightBlock(self, block):
62 def rehighlightBlock(self, block):
63 """ Reimplemented to temporarily enable highlighting if disabled.
63 """ Reimplemented to temporarily enable highlighting if disabled.
64 """
64 """
65 old = self.highlighting_on
65 old = self.highlighting_on
66 self.highlighting_on = True
66 self.highlighting_on = True
67 super(FrontendHighlighter, self).rehighlightBlock(block)
67 super(FrontendHighlighter, self).rehighlightBlock(block)
68 self.highlighting_on = old
68 self.highlighting_on = old
69
69
70 def setFormat(self, start, count, format):
70 def setFormat(self, start, count, format):
71 """ Reimplemented to highlight selectively.
71 """ Reimplemented to highlight selectively.
72 """
72 """
73 start += self._current_offset
73 start += self._current_offset
74 super(FrontendHighlighter, self).setFormat(start, count, format)
74 super(FrontendHighlighter, self).setFormat(start, count, format)
75
75
76
76
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 """ A Qt frontend for a generic Python kernel.
78 """ A Qt frontend for a generic Python kernel.
79 """
79 """
80
80
81 # The text to show when the kernel is (re)started.
81 # The text to show when the kernel is (re)started.
82 banner = Unicode()
82 banner = Unicode()
83
83
84 # An option and corresponding signal for overriding the default kernel
84 # An option and corresponding signal for overriding the default kernel
85 # interrupt behavior.
85 # interrupt behavior.
86 custom_interrupt = Bool(False)
86 custom_interrupt = Bool(False)
87 custom_interrupt_requested = QtCore.Signal()
87 custom_interrupt_requested = QtCore.Signal()
88
88
89 # An option and corresponding signals for overriding the default kernel
89 # An option and corresponding signals for overriding the default kernel
90 # restart behavior.
90 # restart behavior.
91 custom_restart = Bool(False)
91 custom_restart = Bool(False)
92 custom_restart_kernel_died = QtCore.Signal(float)
92 custom_restart_kernel_died = QtCore.Signal(float)
93 custom_restart_requested = QtCore.Signal()
93 custom_restart_requested = QtCore.Signal()
94
94
95 # Whether to automatically show calltips on open-parentheses.
95 # Whether to automatically show calltips on open-parentheses.
96 enable_calltips = Bool(True, config=True,
96 enable_calltips = Bool(True, config=True,
97 help="Whether to draw information calltips on open-parentheses.")
97 help="Whether to draw information calltips on open-parentheses.")
98
98
99 clear_on_kernel_restart = Bool(True, config=True,
99 clear_on_kernel_restart = Bool(True, config=True,
100 help="Whether to clear the console when the kernel is restarted")
100 help="Whether to clear the console when the kernel is restarted")
101
101
102 confirm_restart = Bool(True, config=True,
102 confirm_restart = Bool(True, config=True,
103 help="Whether to ask for user confirmation when restarting kernel")
103 help="Whether to ask for user confirmation when restarting kernel")
104
104
105 # Emitted when a user visible 'execute_request' has been submitted to the
105 # Emitted when a user visible 'execute_request' has been submitted to the
106 # kernel from the FrontendWidget. Contains the code to be executed.
106 # kernel from the FrontendWidget. Contains the code to be executed.
107 executing = QtCore.Signal(object)
107 executing = QtCore.Signal(object)
108
108
109 # Emitted when a user-visible 'execute_reply' has been received from the
109 # Emitted when a user-visible 'execute_reply' has been received from the
110 # kernel and processed by the FrontendWidget. Contains the response message.
110 # kernel and processed by the FrontendWidget. Contains the response message.
111 executed = QtCore.Signal(object)
111 executed = QtCore.Signal(object)
112
112
113 # Emitted when an exit request has been received from the kernel.
113 # Emitted when an exit request has been received from the kernel.
114 exit_requested = QtCore.Signal(object)
114 exit_requested = QtCore.Signal(object)
115
115
116 # Protected class variables.
116 # Protected class variables.
117 _prompt_transformer = IPythonInputSplitter(transforms=[classic_prompt()])
117 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
118 logical_line_transforms=[],
119 python_line_transforms=[],
120 )
118 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
121 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
119 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
122 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
120 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
123 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
121 _input_splitter_class = InputSplitter
124 _input_splitter_class = InputSplitter
122 _local_kernel = False
125 _local_kernel = False
123 _highlighter = Instance(FrontendHighlighter)
126 _highlighter = Instance(FrontendHighlighter)
124
127
125 #---------------------------------------------------------------------------
128 #---------------------------------------------------------------------------
126 # 'object' interface
129 # 'object' interface
127 #---------------------------------------------------------------------------
130 #---------------------------------------------------------------------------
128
131
129 def __init__(self, *args, **kw):
132 def __init__(self, *args, **kw):
130 super(FrontendWidget, self).__init__(*args, **kw)
133 super(FrontendWidget, self).__init__(*args, **kw)
131 # FIXME: remove this when PySide min version is updated past 1.0.7
134 # FIXME: remove this when PySide min version is updated past 1.0.7
132 # forcefully disable calltips if PySide is < 1.0.7, because they crash
135 # forcefully disable calltips if PySide is < 1.0.7, because they crash
133 if qt.QT_API == qt.QT_API_PYSIDE:
136 if qt.QT_API == qt.QT_API_PYSIDE:
134 import PySide
137 import PySide
135 if PySide.__version_info__ < (1,0,7):
138 if PySide.__version_info__ < (1,0,7):
136 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
139 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
137 self.enable_calltips = False
140 self.enable_calltips = False
138
141
139 # FrontendWidget protected variables.
142 # FrontendWidget protected variables.
140 self._bracket_matcher = BracketMatcher(self._control)
143 self._bracket_matcher = BracketMatcher(self._control)
141 self._call_tip_widget = CallTipWidget(self._control)
144 self._call_tip_widget = CallTipWidget(self._control)
142 self._completion_lexer = CompletionLexer(PythonLexer())
145 self._completion_lexer = CompletionLexer(PythonLexer())
143 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
144 self._hidden = False
147 self._hidden = False
145 self._highlighter = FrontendHighlighter(self)
148 self._highlighter = FrontendHighlighter(self)
146 self._input_splitter = self._input_splitter_class(input_mode='cell')
149 self._input_splitter = self._input_splitter_class(input_mode='cell')
147 self._kernel_manager = None
150 self._kernel_manager = None
148 self._request_info = {}
151 self._request_info = {}
149 self._request_info['execute'] = {};
152 self._request_info['execute'] = {};
150 self._callback_dict = {}
153 self._callback_dict = {}
151
154
152 # Configure the ConsoleWidget.
155 # Configure the ConsoleWidget.
153 self.tab_width = 4
156 self.tab_width = 4
154 self._set_continuation_prompt('... ')
157 self._set_continuation_prompt('... ')
155
158
156 # Configure the CallTipWidget.
159 # Configure the CallTipWidget.
157 self._call_tip_widget.setFont(self.font)
160 self._call_tip_widget.setFont(self.font)
158 self.font_changed.connect(self._call_tip_widget.setFont)
161 self.font_changed.connect(self._call_tip_widget.setFont)
159
162
160 # Configure actions.
163 # Configure actions.
161 action = self._copy_raw_action
164 action = self._copy_raw_action
162 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
165 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
163 action.setEnabled(False)
166 action.setEnabled(False)
164 action.setShortcut(QtGui.QKeySequence(key))
167 action.setShortcut(QtGui.QKeySequence(key))
165 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
168 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
166 action.triggered.connect(self.copy_raw)
169 action.triggered.connect(self.copy_raw)
167 self.copy_available.connect(action.setEnabled)
170 self.copy_available.connect(action.setEnabled)
168 self.addAction(action)
171 self.addAction(action)
169
172
170 # Connect signal handlers.
173 # Connect signal handlers.
171 document = self._control.document()
174 document = self._control.document()
172 document.contentsChange.connect(self._document_contents_change)
175 document.contentsChange.connect(self._document_contents_change)
173
176
174 # Set flag for whether we are connected via localhost.
177 # Set flag for whether we are connected via localhost.
175 self._local_kernel = kw.get('local_kernel',
178 self._local_kernel = kw.get('local_kernel',
176 FrontendWidget._local_kernel)
179 FrontendWidget._local_kernel)
177
180
178 #---------------------------------------------------------------------------
181 #---------------------------------------------------------------------------
179 # 'ConsoleWidget' public interface
182 # 'ConsoleWidget' public interface
180 #---------------------------------------------------------------------------
183 #---------------------------------------------------------------------------
181
184
182 def copy(self):
185 def copy(self):
183 """ Copy the currently selected text to the clipboard, removing prompts.
186 """ Copy the currently selected text to the clipboard, removing prompts.
184 """
187 """
185 if self._page_control is not None and self._page_control.hasFocus():
188 if self._page_control is not None and self._page_control.hasFocus():
186 self._page_control.copy()
189 self._page_control.copy()
187 elif self._control.hasFocus():
190 elif self._control.hasFocus():
188 text = self._control.textCursor().selection().toPlainText()
191 text = self._control.textCursor().selection().toPlainText()
189 if text:
192 if text:
190 text = self._prompt_transformer.transform_cell(text)
193 text = self._prompt_transformer.transform_cell(text)
191 QtGui.QApplication.clipboard().setText(text)
194 QtGui.QApplication.clipboard().setText(text)
192 else:
195 else:
193 self.log.debug("frontend widget : unknown copy target")
196 self.log.debug("frontend widget : unknown copy target")
194
197
195 #---------------------------------------------------------------------------
198 #---------------------------------------------------------------------------
196 # 'ConsoleWidget' abstract interface
199 # 'ConsoleWidget' abstract interface
197 #---------------------------------------------------------------------------
200 #---------------------------------------------------------------------------
198
201
199 def _is_complete(self, source, interactive):
202 def _is_complete(self, source, interactive):
200 """ Returns whether 'source' can be completely processed and a new
203 """ Returns whether 'source' can be completely processed and a new
201 prompt created. When triggered by an Enter/Return key press,
204 prompt created. When triggered by an Enter/Return key press,
202 'interactive' is True; otherwise, it is False.
205 'interactive' is True; otherwise, it is False.
203 """
206 """
204 complete = self._input_splitter.push(source)
207 complete = self._input_splitter.push(source)
205 if interactive:
208 if interactive:
206 complete = not self._input_splitter.push_accepts_more()
209 complete = not self._input_splitter.push_accepts_more()
207 return complete
210 return complete
208
211
209 def _execute(self, source, hidden):
212 def _execute(self, source, hidden):
210 """ Execute 'source'. If 'hidden', do not show any output.
213 """ Execute 'source'. If 'hidden', do not show any output.
211
214
212 See parent class :meth:`execute` docstring for full details.
215 See parent class :meth:`execute` docstring for full details.
213 """
216 """
214 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
217 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
215 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
218 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
216 self._hidden = hidden
219 self._hidden = hidden
217 if not hidden:
220 if not hidden:
218 self.executing.emit(source)
221 self.executing.emit(source)
219
222
220 def _prompt_started_hook(self):
223 def _prompt_started_hook(self):
221 """ Called immediately after a new prompt is displayed.
224 """ Called immediately after a new prompt is displayed.
222 """
225 """
223 if not self._reading:
226 if not self._reading:
224 self._highlighter.highlighting_on = True
227 self._highlighter.highlighting_on = True
225
228
226 def _prompt_finished_hook(self):
229 def _prompt_finished_hook(self):
227 """ Called immediately after a prompt is finished, i.e. when some input
230 """ Called immediately after a prompt is finished, i.e. when some input
228 will be processed and a new prompt displayed.
231 will be processed and a new prompt displayed.
229 """
232 """
230 # Flush all state from the input splitter so the next round of
233 # Flush all state from the input splitter so the next round of
231 # reading input starts with a clean buffer.
234 # reading input starts with a clean buffer.
232 self._input_splitter.reset()
235 self._input_splitter.reset()
233
236
234 if not self._reading:
237 if not self._reading:
235 self._highlighter.highlighting_on = False
238 self._highlighter.highlighting_on = False
236
239
237 def _tab_pressed(self):
240 def _tab_pressed(self):
238 """ Called when the tab key is pressed. Returns whether to continue
241 """ Called when the tab key is pressed. Returns whether to continue
239 processing the event.
242 processing the event.
240 """
243 """
241 # Perform tab completion if:
244 # Perform tab completion if:
242 # 1) The cursor is in the input buffer.
245 # 1) The cursor is in the input buffer.
243 # 2) There is a non-whitespace character before the cursor.
246 # 2) There is a non-whitespace character before the cursor.
244 text = self._get_input_buffer_cursor_line()
247 text = self._get_input_buffer_cursor_line()
245 if text is None:
248 if text is None:
246 return False
249 return False
247 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
250 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
248 if complete:
251 if complete:
249 self._complete()
252 self._complete()
250 return not complete
253 return not complete
251
254
252 #---------------------------------------------------------------------------
255 #---------------------------------------------------------------------------
253 # 'ConsoleWidget' protected interface
256 # 'ConsoleWidget' protected interface
254 #---------------------------------------------------------------------------
257 #---------------------------------------------------------------------------
255
258
256 def _context_menu_make(self, pos):
259 def _context_menu_make(self, pos):
257 """ Reimplemented to add an action for raw copy.
260 """ Reimplemented to add an action for raw copy.
258 """
261 """
259 menu = super(FrontendWidget, self)._context_menu_make(pos)
262 menu = super(FrontendWidget, self)._context_menu_make(pos)
260 for before_action in menu.actions():
263 for before_action in menu.actions():
261 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
264 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
262 QtGui.QKeySequence.ExactMatch:
265 QtGui.QKeySequence.ExactMatch:
263 menu.insertAction(before_action, self._copy_raw_action)
266 menu.insertAction(before_action, self._copy_raw_action)
264 break
267 break
265 return menu
268 return menu
266
269
267 def request_interrupt_kernel(self):
270 def request_interrupt_kernel(self):
268 if self._executing:
271 if self._executing:
269 self.interrupt_kernel()
272 self.interrupt_kernel()
270
273
271 def request_restart_kernel(self):
274 def request_restart_kernel(self):
272 message = 'Are you sure you want to restart the kernel?'
275 message = 'Are you sure you want to restart the kernel?'
273 self.restart_kernel(message, now=False)
276 self.restart_kernel(message, now=False)
274
277
275 def _event_filter_console_keypress(self, event):
278 def _event_filter_console_keypress(self, event):
276 """ Reimplemented for execution interruption and smart backspace.
279 """ Reimplemented for execution interruption and smart backspace.
277 """
280 """
278 key = event.key()
281 key = event.key()
279 if self._control_key_down(event.modifiers(), include_command=False):
282 if self._control_key_down(event.modifiers(), include_command=False):
280
283
281 if key == QtCore.Qt.Key_C and self._executing:
284 if key == QtCore.Qt.Key_C and self._executing:
282 self.request_interrupt_kernel()
285 self.request_interrupt_kernel()
283 return True
286 return True
284
287
285 elif key == QtCore.Qt.Key_Period:
288 elif key == QtCore.Qt.Key_Period:
286 self.request_restart_kernel()
289 self.request_restart_kernel()
287 return True
290 return True
288
291
289 elif not event.modifiers() & QtCore.Qt.AltModifier:
292 elif not event.modifiers() & QtCore.Qt.AltModifier:
290
293
291 # Smart backspace: remove four characters in one backspace if:
294 # Smart backspace: remove four characters in one backspace if:
292 # 1) everything left of the cursor is whitespace
295 # 1) everything left of the cursor is whitespace
293 # 2) the four characters immediately left of the cursor are spaces
296 # 2) the four characters immediately left of the cursor are spaces
294 if key == QtCore.Qt.Key_Backspace:
297 if key == QtCore.Qt.Key_Backspace:
295 col = self._get_input_buffer_cursor_column()
298 col = self._get_input_buffer_cursor_column()
296 cursor = self._control.textCursor()
299 cursor = self._control.textCursor()
297 if col > 3 and not cursor.hasSelection():
300 if col > 3 and not cursor.hasSelection():
298 text = self._get_input_buffer_cursor_line()[:col]
301 text = self._get_input_buffer_cursor_line()[:col]
299 if text.endswith(' ') and not text.strip():
302 if text.endswith(' ') and not text.strip():
300 cursor.movePosition(QtGui.QTextCursor.Left,
303 cursor.movePosition(QtGui.QTextCursor.Left,
301 QtGui.QTextCursor.KeepAnchor, 4)
304 QtGui.QTextCursor.KeepAnchor, 4)
302 cursor.removeSelectedText()
305 cursor.removeSelectedText()
303 return True
306 return True
304
307
305 return super(FrontendWidget, self)._event_filter_console_keypress(event)
308 return super(FrontendWidget, self)._event_filter_console_keypress(event)
306
309
307 def _insert_continuation_prompt(self, cursor):
310 def _insert_continuation_prompt(self, cursor):
308 """ Reimplemented for auto-indentation.
311 """ Reimplemented for auto-indentation.
309 """
312 """
310 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
313 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
311 cursor.insertText(' ' * self._input_splitter.indent_spaces)
314 cursor.insertText(' ' * self._input_splitter.indent_spaces)
312
315
313 #---------------------------------------------------------------------------
316 #---------------------------------------------------------------------------
314 # 'BaseFrontendMixin' abstract interface
317 # 'BaseFrontendMixin' abstract interface
315 #---------------------------------------------------------------------------
318 #---------------------------------------------------------------------------
316
319
317 def _handle_complete_reply(self, rep):
320 def _handle_complete_reply(self, rep):
318 """ Handle replies for tab completion.
321 """ Handle replies for tab completion.
319 """
322 """
320 self.log.debug("complete: %s", rep.get('content', ''))
323 self.log.debug("complete: %s", rep.get('content', ''))
321 cursor = self._get_cursor()
324 cursor = self._get_cursor()
322 info = self._request_info.get('complete')
325 info = self._request_info.get('complete')
323 if info and info.id == rep['parent_header']['msg_id'] and \
326 if info and info.id == rep['parent_header']['msg_id'] and \
324 info.pos == cursor.position():
327 info.pos == cursor.position():
325 text = '.'.join(self._get_context())
328 text = '.'.join(self._get_context())
326 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
329 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
327 self._complete_with_items(cursor, rep['content']['matches'])
330 self._complete_with_items(cursor, rep['content']['matches'])
328
331
329 def _silent_exec_callback(self, expr, callback):
332 def _silent_exec_callback(self, expr, callback):
330 """Silently execute `expr` in the kernel and call `callback` with reply
333 """Silently execute `expr` in the kernel and call `callback` with reply
331
334
332 the `expr` is evaluated silently in the kernel (without) output in
335 the `expr` is evaluated silently in the kernel (without) output in
333 the frontend. Call `callback` with the
336 the frontend. Call `callback` with the
334 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
337 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
335
338
336 Parameters
339 Parameters
337 ----------
340 ----------
338 expr : string
341 expr : string
339 valid string to be executed by the kernel.
342 valid string to be executed by the kernel.
340 callback : function
343 callback : function
341 function accepting one argument, as a string. The string will be
344 function accepting one argument, as a string. The string will be
342 the `repr` of the result of evaluating `expr`
345 the `repr` of the result of evaluating `expr`
343
346
344 The `callback` is called with the `repr()` of the result of `expr` as
347 The `callback` is called with the `repr()` of the result of `expr` as
345 first argument. To get the object, do `eval()` on the passed value.
348 first argument. To get the object, do `eval()` on the passed value.
346
349
347 See Also
350 See Also
348 --------
351 --------
349 _handle_exec_callback : private method, deal with calling callback with reply
352 _handle_exec_callback : private method, deal with calling callback with reply
350
353
351 """
354 """
352
355
353 # generate uuid, which would be used as an indication of whether or
356 # generate uuid, which would be used as an indication of whether or
354 # not the unique request originated from here (can use msg id ?)
357 # not the unique request originated from here (can use msg id ?)
355 local_uuid = str(uuid.uuid1())
358 local_uuid = str(uuid.uuid1())
356 msg_id = self.kernel_manager.shell_channel.execute('',
359 msg_id = self.kernel_manager.shell_channel.execute('',
357 silent=True, user_expressions={ local_uuid:expr })
360 silent=True, user_expressions={ local_uuid:expr })
358 self._callback_dict[local_uuid] = callback
361 self._callback_dict[local_uuid] = callback
359 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
362 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
360
363
361 def _handle_exec_callback(self, msg):
364 def _handle_exec_callback(self, msg):
362 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
365 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
363
366
364 Parameters
367 Parameters
365 ----------
368 ----------
366 msg : raw message send by the kernel containing an `user_expressions`
369 msg : raw message send by the kernel containing an `user_expressions`
367 and having a 'silent_exec_callback' kind.
370 and having a 'silent_exec_callback' kind.
368
371
369 Notes
372 Notes
370 -----
373 -----
371 This function will look for a `callback` associated with the
374 This function will look for a `callback` associated with the
372 corresponding message id. Association has been made by
375 corresponding message id. Association has been made by
373 `_silent_exec_callback`. `callback` is then called with the `repr()`
376 `_silent_exec_callback`. `callback` is then called with the `repr()`
374 of the value of corresponding `user_expressions` as argument.
377 of the value of corresponding `user_expressions` as argument.
375 `callback` is then removed from the known list so that any message
378 `callback` is then removed from the known list so that any message
376 coming again with the same id won't trigger it.
379 coming again with the same id won't trigger it.
377
380
378 """
381 """
379
382
380 user_exp = msg['content'].get('user_expressions')
383 user_exp = msg['content'].get('user_expressions')
381 if not user_exp:
384 if not user_exp:
382 return
385 return
383 for expression in user_exp:
386 for expression in user_exp:
384 if expression in self._callback_dict:
387 if expression in self._callback_dict:
385 self._callback_dict.pop(expression)(user_exp[expression])
388 self._callback_dict.pop(expression)(user_exp[expression])
386
389
387 def _handle_execute_reply(self, msg):
390 def _handle_execute_reply(self, msg):
388 """ Handles replies for code execution.
391 """ Handles replies for code execution.
389 """
392 """
390 self.log.debug("execute: %s", msg.get('content', ''))
393 self.log.debug("execute: %s", msg.get('content', ''))
391 msg_id = msg['parent_header']['msg_id']
394 msg_id = msg['parent_header']['msg_id']
392 info = self._request_info['execute'].get(msg_id)
395 info = self._request_info['execute'].get(msg_id)
393 # unset reading flag, because if execute finished, raw_input can't
396 # unset reading flag, because if execute finished, raw_input can't
394 # still be pending.
397 # still be pending.
395 self._reading = False
398 self._reading = False
396 if info and info.kind == 'user' and not self._hidden:
399 if info and info.kind == 'user' and not self._hidden:
397 # Make sure that all output from the SUB channel has been processed
400 # Make sure that all output from the SUB channel has been processed
398 # before writing a new prompt.
401 # before writing a new prompt.
399 self.kernel_manager.iopub_channel.flush()
402 self.kernel_manager.iopub_channel.flush()
400
403
401 # Reset the ANSI style information to prevent bad text in stdout
404 # Reset the ANSI style information to prevent bad text in stdout
402 # from messing up our colors. We're not a true terminal so we're
405 # from messing up our colors. We're not a true terminal so we're
403 # allowed to do this.
406 # allowed to do this.
404 if self.ansi_codes:
407 if self.ansi_codes:
405 self._ansi_processor.reset_sgr()
408 self._ansi_processor.reset_sgr()
406
409
407 content = msg['content']
410 content = msg['content']
408 status = content['status']
411 status = content['status']
409 if status == 'ok':
412 if status == 'ok':
410 self._process_execute_ok(msg)
413 self._process_execute_ok(msg)
411 elif status == 'error':
414 elif status == 'error':
412 self._process_execute_error(msg)
415 self._process_execute_error(msg)
413 elif status == 'aborted':
416 elif status == 'aborted':
414 self._process_execute_abort(msg)
417 self._process_execute_abort(msg)
415
418
416 self._show_interpreter_prompt_for_reply(msg)
419 self._show_interpreter_prompt_for_reply(msg)
417 self.executed.emit(msg)
420 self.executed.emit(msg)
418 self._request_info['execute'].pop(msg_id)
421 self._request_info['execute'].pop(msg_id)
419 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
422 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
420 self._handle_exec_callback(msg)
423 self._handle_exec_callback(msg)
421 self._request_info['execute'].pop(msg_id)
424 self._request_info['execute'].pop(msg_id)
422 else:
425 else:
423 super(FrontendWidget, self)._handle_execute_reply(msg)
426 super(FrontendWidget, self)._handle_execute_reply(msg)
424
427
425 def _handle_input_request(self, msg):
428 def _handle_input_request(self, msg):
426 """ Handle requests for raw_input.
429 """ Handle requests for raw_input.
427 """
430 """
428 self.log.debug("input: %s", msg.get('content', ''))
431 self.log.debug("input: %s", msg.get('content', ''))
429 if self._hidden:
432 if self._hidden:
430 raise RuntimeError('Request for raw input during hidden execution.')
433 raise RuntimeError('Request for raw input during hidden execution.')
431
434
432 # Make sure that all output from the SUB channel has been processed
435 # Make sure that all output from the SUB channel has been processed
433 # before entering readline mode.
436 # before entering readline mode.
434 self.kernel_manager.iopub_channel.flush()
437 self.kernel_manager.iopub_channel.flush()
435
438
436 def callback(line):
439 def callback(line):
437 self.kernel_manager.stdin_channel.input(line)
440 self.kernel_manager.stdin_channel.input(line)
438 if self._reading:
441 if self._reading:
439 self.log.debug("Got second input request, assuming first was interrupted.")
442 self.log.debug("Got second input request, assuming first was interrupted.")
440 self._reading = False
443 self._reading = False
441 self._readline(msg['content']['prompt'], callback=callback)
444 self._readline(msg['content']['prompt'], callback=callback)
442
445
443 def _handle_kernel_died(self, since_last_heartbeat):
446 def _handle_kernel_died(self, since_last_heartbeat):
444 """ Handle the kernel's death by asking if the user wants to restart.
447 """ Handle the kernel's death by asking if the user wants to restart.
445 """
448 """
446 self.log.debug("kernel died: %s", since_last_heartbeat)
449 self.log.debug("kernel died: %s", since_last_heartbeat)
447 if self.custom_restart:
450 if self.custom_restart:
448 self.custom_restart_kernel_died.emit(since_last_heartbeat)
451 self.custom_restart_kernel_died.emit(since_last_heartbeat)
449 else:
452 else:
450 message = 'The kernel heartbeat has been inactive for %.2f ' \
453 message = 'The kernel heartbeat has been inactive for %.2f ' \
451 'seconds. Do you want to restart the kernel? You may ' \
454 'seconds. Do you want to restart the kernel? You may ' \
452 'first want to check the network connection.' % \
455 'first want to check the network connection.' % \
453 since_last_heartbeat
456 since_last_heartbeat
454 self.restart_kernel(message, now=True)
457 self.restart_kernel(message, now=True)
455
458
456 def _handle_object_info_reply(self, rep):
459 def _handle_object_info_reply(self, rep):
457 """ Handle replies for call tips.
460 """ Handle replies for call tips.
458 """
461 """
459 self.log.debug("oinfo: %s", rep.get('content', ''))
462 self.log.debug("oinfo: %s", rep.get('content', ''))
460 cursor = self._get_cursor()
463 cursor = self._get_cursor()
461 info = self._request_info.get('call_tip')
464 info = self._request_info.get('call_tip')
462 if info and info.id == rep['parent_header']['msg_id'] and \
465 if info and info.id == rep['parent_header']['msg_id'] and \
463 info.pos == cursor.position():
466 info.pos == cursor.position():
464 # Get the information for a call tip. For now we format the call
467 # Get the information for a call tip. For now we format the call
465 # line as string, later we can pass False to format_call and
468 # line as string, later we can pass False to format_call and
466 # syntax-highlight it ourselves for nicer formatting in the
469 # syntax-highlight it ourselves for nicer formatting in the
467 # calltip.
470 # calltip.
468 content = rep['content']
471 content = rep['content']
469 # if this is from pykernel, 'docstring' will be the only key
472 # if this is from pykernel, 'docstring' will be the only key
470 if content.get('ismagic', False):
473 if content.get('ismagic', False):
471 # Don't generate a call-tip for magics. Ideally, we should
474 # Don't generate a call-tip for magics. Ideally, we should
472 # generate a tooltip, but not on ( like we do for actual
475 # generate a tooltip, but not on ( like we do for actual
473 # callables.
476 # callables.
474 call_info, doc = None, None
477 call_info, doc = None, None
475 else:
478 else:
476 call_info, doc = call_tip(content, format_call=True)
479 call_info, doc = call_tip(content, format_call=True)
477 if call_info or doc:
480 if call_info or doc:
478 self._call_tip_widget.show_call_info(call_info, doc)
481 self._call_tip_widget.show_call_info(call_info, doc)
479
482
480 def _handle_pyout(self, msg):
483 def _handle_pyout(self, msg):
481 """ Handle display hook output.
484 """ Handle display hook output.
482 """
485 """
483 self.log.debug("pyout: %s", msg.get('content', ''))
486 self.log.debug("pyout: %s", msg.get('content', ''))
484 if not self._hidden and self._is_from_this_session(msg):
487 if not self._hidden and self._is_from_this_session(msg):
485 text = msg['content']['data']
488 text = msg['content']['data']
486 self._append_plain_text(text + '\n', before_prompt=True)
489 self._append_plain_text(text + '\n', before_prompt=True)
487
490
488 def _handle_stream(self, msg):
491 def _handle_stream(self, msg):
489 """ Handle stdout, stderr, and stdin.
492 """ Handle stdout, stderr, and stdin.
490 """
493 """
491 self.log.debug("stream: %s", msg.get('content', ''))
494 self.log.debug("stream: %s", msg.get('content', ''))
492 if not self._hidden and self._is_from_this_session(msg):
495 if not self._hidden and self._is_from_this_session(msg):
493 # Most consoles treat tabs as being 8 space characters. Convert tabs
496 # Most consoles treat tabs as being 8 space characters. Convert tabs
494 # to spaces so that output looks as expected regardless of this
497 # to spaces so that output looks as expected regardless of this
495 # widget's tab width.
498 # widget's tab width.
496 text = msg['content']['data'].expandtabs(8)
499 text = msg['content']['data'].expandtabs(8)
497
500
498 self._append_plain_text(text, before_prompt=True)
501 self._append_plain_text(text, before_prompt=True)
499 self._control.moveCursor(QtGui.QTextCursor.End)
502 self._control.moveCursor(QtGui.QTextCursor.End)
500
503
501 def _handle_shutdown_reply(self, msg):
504 def _handle_shutdown_reply(self, msg):
502 """ Handle shutdown signal, only if from other console.
505 """ Handle shutdown signal, only if from other console.
503 """
506 """
504 self.log.debug("shutdown: %s", msg.get('content', ''))
507 self.log.debug("shutdown: %s", msg.get('content', ''))
505 if not self._hidden and not self._is_from_this_session(msg):
508 if not self._hidden and not self._is_from_this_session(msg):
506 if self._local_kernel:
509 if self._local_kernel:
507 if not msg['content']['restart']:
510 if not msg['content']['restart']:
508 self.exit_requested.emit(self)
511 self.exit_requested.emit(self)
509 else:
512 else:
510 # we just got notified of a restart!
513 # we just got notified of a restart!
511 time.sleep(0.25) # wait 1/4 sec to reset
514 time.sleep(0.25) # wait 1/4 sec to reset
512 # lest the request for a new prompt
515 # lest the request for a new prompt
513 # goes to the old kernel
516 # goes to the old kernel
514 self.reset()
517 self.reset()
515 else: # remote kernel, prompt on Kernel shutdown/reset
518 else: # remote kernel, prompt on Kernel shutdown/reset
516 title = self.window().windowTitle()
519 title = self.window().windowTitle()
517 if not msg['content']['restart']:
520 if not msg['content']['restart']:
518 reply = QtGui.QMessageBox.question(self, title,
521 reply = QtGui.QMessageBox.question(self, title,
519 "Kernel has been shutdown permanently. "
522 "Kernel has been shutdown permanently. "
520 "Close the Console?",
523 "Close the Console?",
521 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
524 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
522 if reply == QtGui.QMessageBox.Yes:
525 if reply == QtGui.QMessageBox.Yes:
523 self.exit_requested.emit(self)
526 self.exit_requested.emit(self)
524 else:
527 else:
525 # XXX: remove message box in favor of using the
528 # XXX: remove message box in favor of using the
526 # clear_on_kernel_restart setting?
529 # clear_on_kernel_restart setting?
527 reply = QtGui.QMessageBox.question(self, title,
530 reply = QtGui.QMessageBox.question(self, title,
528 "Kernel has been reset. Clear the Console?",
531 "Kernel has been reset. Clear the Console?",
529 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
532 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
530 if reply == QtGui.QMessageBox.Yes:
533 if reply == QtGui.QMessageBox.Yes:
531 time.sleep(0.25) # wait 1/4 sec to reset
534 time.sleep(0.25) # wait 1/4 sec to reset
532 # lest the request for a new prompt
535 # lest the request for a new prompt
533 # goes to the old kernel
536 # goes to the old kernel
534 self.reset()
537 self.reset()
535
538
536 def _started_channels(self):
539 def _started_channels(self):
537 """ Called when the KernelManager channels have started listening or
540 """ Called when the KernelManager channels have started listening or
538 when the frontend is assigned an already listening KernelManager.
541 when the frontend is assigned an already listening KernelManager.
539 """
542 """
540 self.reset(clear=True)
543 self.reset(clear=True)
541
544
542 #---------------------------------------------------------------------------
545 #---------------------------------------------------------------------------
543 # 'FrontendWidget' public interface
546 # 'FrontendWidget' public interface
544 #---------------------------------------------------------------------------
547 #---------------------------------------------------------------------------
545
548
546 def copy_raw(self):
549 def copy_raw(self):
547 """ Copy the currently selected text to the clipboard without attempting
550 """ Copy the currently selected text to the clipboard without attempting
548 to remove prompts or otherwise alter the text.
551 to remove prompts or otherwise alter the text.
549 """
552 """
550 self._control.copy()
553 self._control.copy()
551
554
552 def execute_file(self, path, hidden=False):
555 def execute_file(self, path, hidden=False):
553 """ Attempts to execute file with 'path'. If 'hidden', no output is
556 """ Attempts to execute file with 'path'. If 'hidden', no output is
554 shown.
557 shown.
555 """
558 """
556 self.execute('execfile(%r)' % path, hidden=hidden)
559 self.execute('execfile(%r)' % path, hidden=hidden)
557
560
558 def interrupt_kernel(self):
561 def interrupt_kernel(self):
559 """ Attempts to interrupt the running kernel.
562 """ Attempts to interrupt the running kernel.
560
563
561 Also unsets _reading flag, to avoid runtime errors
564 Also unsets _reading flag, to avoid runtime errors
562 if raw_input is called again.
565 if raw_input is called again.
563 """
566 """
564 if self.custom_interrupt:
567 if self.custom_interrupt:
565 self._reading = False
568 self._reading = False
566 self.custom_interrupt_requested.emit()
569 self.custom_interrupt_requested.emit()
567 elif self.kernel_manager.has_kernel:
570 elif self.kernel_manager.has_kernel:
568 self._reading = False
571 self._reading = False
569 self.kernel_manager.interrupt_kernel()
572 self.kernel_manager.interrupt_kernel()
570 else:
573 else:
571 self._append_plain_text('Kernel process is either remote or '
574 self._append_plain_text('Kernel process is either remote or '
572 'unspecified. Cannot interrupt.\n')
575 'unspecified. Cannot interrupt.\n')
573
576
574 def reset(self, clear=False):
577 def reset(self, clear=False):
575 """ Resets the widget to its initial state if ``clear`` parameter or
578 """ Resets the widget to its initial state if ``clear`` parameter or
576 ``clear_on_kernel_restart`` configuration setting is True, otherwise
579 ``clear_on_kernel_restart`` configuration setting is True, otherwise
577 prints a visual indication of the fact that the kernel restarted, but
580 prints a visual indication of the fact that the kernel restarted, but
578 does not clear the traces from previous usage of the kernel before it
581 does not clear the traces from previous usage of the kernel before it
579 was restarted. With ``clear=True``, it is similar to ``%clear``, but
582 was restarted. With ``clear=True``, it is similar to ``%clear``, but
580 also re-writes the banner and aborts execution if necessary.
583 also re-writes the banner and aborts execution if necessary.
581 """
584 """
582 if self._executing:
585 if self._executing:
583 self._executing = False
586 self._executing = False
584 self._request_info['execute'] = {}
587 self._request_info['execute'] = {}
585 self._reading = False
588 self._reading = False
586 self._highlighter.highlighting_on = False
589 self._highlighter.highlighting_on = False
587
590
588 if self.clear_on_kernel_restart or clear:
591 if self.clear_on_kernel_restart or clear:
589 self._control.clear()
592 self._control.clear()
590 self._append_plain_text(self.banner)
593 self._append_plain_text(self.banner)
591 else:
594 else:
592 self._append_plain_text("# restarting kernel...")
595 self._append_plain_text("# restarting kernel...")
593 self._append_html("<hr><br>")
596 self._append_html("<hr><br>")
594 # XXX: Reprinting the full banner may be too much, but once #1680 is
597 # XXX: Reprinting the full banner may be too much, but once #1680 is
595 # addressed, that will mitigate it.
598 # addressed, that will mitigate it.
596 #self._append_plain_text(self.banner)
599 #self._append_plain_text(self.banner)
597 # update output marker for stdout/stderr, so that startup
600 # update output marker for stdout/stderr, so that startup
598 # messages appear after banner:
601 # messages appear after banner:
599 self._append_before_prompt_pos = self._get_cursor().position()
602 self._append_before_prompt_pos = self._get_cursor().position()
600 self._show_interpreter_prompt()
603 self._show_interpreter_prompt()
601
604
602 def restart_kernel(self, message, now=False):
605 def restart_kernel(self, message, now=False):
603 """ Attempts to restart the running kernel.
606 """ Attempts to restart the running kernel.
604 """
607 """
605 # FIXME: now should be configurable via a checkbox in the dialog. Right
608 # FIXME: now should be configurable via a checkbox in the dialog. Right
606 # now at least the heartbeat path sets it to True and the manual restart
609 # now at least the heartbeat path sets it to True and the manual restart
607 # to False. But those should just be the pre-selected states of a
610 # to False. But those should just be the pre-selected states of a
608 # checkbox that the user could override if so desired. But I don't know
611 # checkbox that the user could override if so desired. But I don't know
609 # enough Qt to go implementing the checkbox now.
612 # enough Qt to go implementing the checkbox now.
610
613
611 if self.custom_restart:
614 if self.custom_restart:
612 self.custom_restart_requested.emit()
615 self.custom_restart_requested.emit()
613
616
614 elif self.kernel_manager.has_kernel:
617 elif self.kernel_manager.has_kernel:
615 # Pause the heart beat channel to prevent further warnings.
618 # Pause the heart beat channel to prevent further warnings.
616 self.kernel_manager.hb_channel.pause()
619 self.kernel_manager.hb_channel.pause()
617
620
618 # Prompt the user to restart the kernel. Un-pause the heartbeat if
621 # Prompt the user to restart the kernel. Un-pause the heartbeat if
619 # they decline. (If they accept, the heartbeat will be un-paused
622 # they decline. (If they accept, the heartbeat will be un-paused
620 # automatically when the kernel is restarted.)
623 # automatically when the kernel is restarted.)
621 if self.confirm_restart:
624 if self.confirm_restart:
622 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
625 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
623 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
626 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
624 message, buttons)
627 message, buttons)
625 do_restart = result == QtGui.QMessageBox.Yes
628 do_restart = result == QtGui.QMessageBox.Yes
626 else:
629 else:
627 # confirm_restart is False, so we don't need to ask user
630 # confirm_restart is False, so we don't need to ask user
628 # anything, just do the restart
631 # anything, just do the restart
629 do_restart = True
632 do_restart = True
630 if do_restart:
633 if do_restart:
631 try:
634 try:
632 self.kernel_manager.restart_kernel(now=now)
635 self.kernel_manager.restart_kernel(now=now)
633 except RuntimeError:
636 except RuntimeError:
634 self._append_plain_text('Kernel started externally. '
637 self._append_plain_text('Kernel started externally. '
635 'Cannot restart.\n',
638 'Cannot restart.\n',
636 before_prompt=True
639 before_prompt=True
637 )
640 )
638 else:
641 else:
639 self.reset()
642 self.reset()
640 else:
643 else:
641 self.kernel_manager.hb_channel.unpause()
644 self.kernel_manager.hb_channel.unpause()
642
645
643 else:
646 else:
644 self._append_plain_text('Kernel process is either remote or '
647 self._append_plain_text('Kernel process is either remote or '
645 'unspecified. Cannot restart.\n',
648 'unspecified. Cannot restart.\n',
646 before_prompt=True
649 before_prompt=True
647 )
650 )
648
651
649 #---------------------------------------------------------------------------
652 #---------------------------------------------------------------------------
650 # 'FrontendWidget' protected interface
653 # 'FrontendWidget' protected interface
651 #---------------------------------------------------------------------------
654 #---------------------------------------------------------------------------
652
655
653 def _call_tip(self):
656 def _call_tip(self):
654 """ Shows a call tip, if appropriate, at the current cursor location.
657 """ Shows a call tip, if appropriate, at the current cursor location.
655 """
658 """
656 # Decide if it makes sense to show a call tip
659 # Decide if it makes sense to show a call tip
657 if not self.enable_calltips:
660 if not self.enable_calltips:
658 return False
661 return False
659 cursor = self._get_cursor()
662 cursor = self._get_cursor()
660 cursor.movePosition(QtGui.QTextCursor.Left)
663 cursor.movePosition(QtGui.QTextCursor.Left)
661 if cursor.document().characterAt(cursor.position()) != '(':
664 if cursor.document().characterAt(cursor.position()) != '(':
662 return False
665 return False
663 context = self._get_context(cursor)
666 context = self._get_context(cursor)
664 if not context:
667 if not context:
665 return False
668 return False
666
669
667 # Send the metadata request to the kernel
670 # Send the metadata request to the kernel
668 name = '.'.join(context)
671 name = '.'.join(context)
669 msg_id = self.kernel_manager.shell_channel.object_info(name)
672 msg_id = self.kernel_manager.shell_channel.object_info(name)
670 pos = self._get_cursor().position()
673 pos = self._get_cursor().position()
671 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
674 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
672 return True
675 return True
673
676
674 def _complete(self):
677 def _complete(self):
675 """ Performs completion at the current cursor location.
678 """ Performs completion at the current cursor location.
676 """
679 """
677 context = self._get_context()
680 context = self._get_context()
678 if context:
681 if context:
679 # Send the completion request to the kernel
682 # Send the completion request to the kernel
680 msg_id = self.kernel_manager.shell_channel.complete(
683 msg_id = self.kernel_manager.shell_channel.complete(
681 '.'.join(context), # text
684 '.'.join(context), # text
682 self._get_input_buffer_cursor_line(), # line
685 self._get_input_buffer_cursor_line(), # line
683 self._get_input_buffer_cursor_column(), # cursor_pos
686 self._get_input_buffer_cursor_column(), # cursor_pos
684 self.input_buffer) # block
687 self.input_buffer) # block
685 pos = self._get_cursor().position()
688 pos = self._get_cursor().position()
686 info = self._CompletionRequest(msg_id, pos)
689 info = self._CompletionRequest(msg_id, pos)
687 self._request_info['complete'] = info
690 self._request_info['complete'] = info
688
691
689 def _get_context(self, cursor=None):
692 def _get_context(self, cursor=None):
690 """ Gets the context for the specified cursor (or the current cursor
693 """ Gets the context for the specified cursor (or the current cursor
691 if none is specified).
694 if none is specified).
692 """
695 """
693 if cursor is None:
696 if cursor is None:
694 cursor = self._get_cursor()
697 cursor = self._get_cursor()
695 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
698 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
696 QtGui.QTextCursor.KeepAnchor)
699 QtGui.QTextCursor.KeepAnchor)
697 text = cursor.selection().toPlainText()
700 text = cursor.selection().toPlainText()
698 return self._completion_lexer.get_context(text)
701 return self._completion_lexer.get_context(text)
699
702
700 def _process_execute_abort(self, msg):
703 def _process_execute_abort(self, msg):
701 """ Process a reply for an aborted execution request.
704 """ Process a reply for an aborted execution request.
702 """
705 """
703 self._append_plain_text("ERROR: execution aborted\n")
706 self._append_plain_text("ERROR: execution aborted\n")
704
707
705 def _process_execute_error(self, msg):
708 def _process_execute_error(self, msg):
706 """ Process a reply for an execution request that resulted in an error.
709 """ Process a reply for an execution request that resulted in an error.
707 """
710 """
708 content = msg['content']
711 content = msg['content']
709 # If a SystemExit is passed along, this means exit() was called - also
712 # If a SystemExit is passed along, this means exit() was called - also
710 # all the ipython %exit magic syntax of '-k' to be used to keep
713 # all the ipython %exit magic syntax of '-k' to be used to keep
711 # the kernel running
714 # the kernel running
712 if content['ename']=='SystemExit':
715 if content['ename']=='SystemExit':
713 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
716 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
714 self._keep_kernel_on_exit = keepkernel
717 self._keep_kernel_on_exit = keepkernel
715 self.exit_requested.emit(self)
718 self.exit_requested.emit(self)
716 else:
719 else:
717 traceback = ''.join(content['traceback'])
720 traceback = ''.join(content['traceback'])
718 self._append_plain_text(traceback)
721 self._append_plain_text(traceback)
719
722
720 def _process_execute_ok(self, msg):
723 def _process_execute_ok(self, msg):
721 """ Process a reply for a successful execution request.
724 """ Process a reply for a successful execution request.
722 """
725 """
723 payload = msg['content']['payload']
726 payload = msg['content']['payload']
724 for item in payload:
727 for item in payload:
725 if not self._process_execute_payload(item):
728 if not self._process_execute_payload(item):
726 warning = 'Warning: received unknown payload of type %s'
729 warning = 'Warning: received unknown payload of type %s'
727 print(warning % repr(item['source']))
730 print(warning % repr(item['source']))
728
731
729 def _process_execute_payload(self, item):
732 def _process_execute_payload(self, item):
730 """ Process a single payload item from the list of payload items in an
733 """ Process a single payload item from the list of payload items in an
731 execution reply. Returns whether the payload was handled.
734 execution reply. Returns whether the payload was handled.
732 """
735 """
733 # The basic FrontendWidget doesn't handle payloads, as they are a
736 # The basic FrontendWidget doesn't handle payloads, as they are a
734 # mechanism for going beyond the standard Python interpreter model.
737 # mechanism for going beyond the standard Python interpreter model.
735 return False
738 return False
736
739
737 def _show_interpreter_prompt(self):
740 def _show_interpreter_prompt(self):
738 """ Shows a prompt for the interpreter.
741 """ Shows a prompt for the interpreter.
739 """
742 """
740 self._show_prompt('>>> ')
743 self._show_prompt('>>> ')
741
744
742 def _show_interpreter_prompt_for_reply(self, msg):
745 def _show_interpreter_prompt_for_reply(self, msg):
743 """ Shows a prompt for the interpreter given an 'execute_reply' message.
746 """ Shows a prompt for the interpreter given an 'execute_reply' message.
744 """
747 """
745 self._show_interpreter_prompt()
748 self._show_interpreter_prompt()
746
749
747 #------ Signal handlers ----------------------------------------------------
750 #------ Signal handlers ----------------------------------------------------
748
751
749 def _document_contents_change(self, position, removed, added):
752 def _document_contents_change(self, position, removed, added):
750 """ Called whenever the document's content changes. Display a call tip
753 """ Called whenever the document's content changes. Display a call tip
751 if appropriate.
754 if appropriate.
752 """
755 """
753 # Calculate where the cursor should be *after* the change:
756 # Calculate where the cursor should be *after* the change:
754 position += added
757 position += added
755
758
756 document = self._control.document()
759 document = self._control.document()
757 if position == self._get_cursor().position():
760 if position == self._get_cursor().position():
758 self._call_tip()
761 self._call_tip()
759
762
760 #------ Trait default initializers -----------------------------------------
763 #------ Trait default initializers -----------------------------------------
761
764
762 def _banner_default(self):
765 def _banner_default(self):
763 """ Returns the standard Python banner.
766 """ Returns the standard Python banner.
764 """
767 """
765 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
768 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
766 '"license" for more information.'
769 '"license" for more information.'
767 return banner % (sys.version, sys.platform)
770 return banner % (sys.version, sys.platform)
@@ -1,581 +1,584 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Imports
6 # Imports
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # Standard library imports
9 # Standard library imports
10 from collections import namedtuple
10 from collections import namedtuple
11 import os.path
11 import os.path
12 import re
12 import re
13 from subprocess import Popen
13 from subprocess import Popen
14 import sys
14 import sys
15 import time
15 import time
16 from textwrap import dedent
16 from textwrap import dedent
17
17
18 # System library imports
18 # System library imports
19 from IPython.external.qt import QtCore, QtGui
19 from IPython.external.qt import QtCore, QtGui
20
20
21 # Local imports
21 # Local imports
22 from IPython.core.inputsplitter import IPythonInputSplitter
22 from IPython.core.inputsplitter import IPythonInputSplitter
23 from IPython.core.inputtransformer import ipy_prompt
23 from IPython.core.inputtransformer import ipy_prompt
24 from IPython.utils.traitlets import Bool, Unicode
24 from IPython.utils.traitlets import Bool, Unicode
25 from frontend_widget import FrontendWidget
25 from frontend_widget import FrontendWidget
26 import styles
26 import styles
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Constants
29 # Constants
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 # Default strings to build and display input and output prompts (and separators
32 # Default strings to build and display input and output prompts (and separators
33 # in between)
33 # in between)
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36 default_input_sep = '\n'
36 default_input_sep = '\n'
37 default_output_sep = ''
37 default_output_sep = ''
38 default_output_sep2 = ''
38 default_output_sep2 = ''
39
39
40 # Base path for most payload sources.
40 # Base path for most payload sources.
41 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
41 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
42
42
43 if sys.platform.startswith('win'):
43 if sys.platform.startswith('win'):
44 default_editor = 'notepad'
44 default_editor = 'notepad'
45 else:
45 else:
46 default_editor = ''
46 default_editor = ''
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # IPythonWidget class
49 # IPythonWidget class
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 class IPythonWidget(FrontendWidget):
52 class IPythonWidget(FrontendWidget):
53 """ A FrontendWidget for an IPython kernel.
53 """ A FrontendWidget for an IPython kernel.
54 """
54 """
55
55
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
58 # settings.
58 # settings.
59 custom_edit = Bool(False)
59 custom_edit = Bool(False)
60 custom_edit_requested = QtCore.Signal(object, object)
60 custom_edit_requested = QtCore.Signal(object, object)
61
61
62 editor = Unicode(default_editor, config=True,
62 editor = Unicode(default_editor, config=True,
63 help="""
63 help="""
64 A command for invoking a system text editor. If the string contains a
64 A command for invoking a system text editor. If the string contains a
65 {filename} format specifier, it will be used. Otherwise, the filename
65 {filename} format specifier, it will be used. Otherwise, the filename
66 will be appended to the end the command.
66 will be appended to the end the command.
67 """)
67 """)
68
68
69 editor_line = Unicode(config=True,
69 editor_line = Unicode(config=True,
70 help="""
70 help="""
71 The editor command to use when a specific line number is requested. The
71 The editor command to use when a specific line number is requested. The
72 string should contain two format specifiers: {line} and {filename}. If
72 string should contain two format specifiers: {line} and {filename}. If
73 this parameter is not specified, the line number option to the %edit
73 this parameter is not specified, the line number option to the %edit
74 magic will be ignored.
74 magic will be ignored.
75 """)
75 """)
76
76
77 style_sheet = Unicode(config=True,
77 style_sheet = Unicode(config=True,
78 help="""
78 help="""
79 A CSS stylesheet. The stylesheet can contain classes for:
79 A CSS stylesheet. The stylesheet can contain classes for:
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
82 3. IPython: .error, .in-prompt, .out-prompt, etc
82 3. IPython: .error, .in-prompt, .out-prompt, etc
83 """)
83 """)
84
84
85 syntax_style = Unicode(config=True,
85 syntax_style = Unicode(config=True,
86 help="""
86 help="""
87 If not empty, use this Pygments style for syntax highlighting.
87 If not empty, use this Pygments style for syntax highlighting.
88 Otherwise, the style sheet is queried for Pygments style
88 Otherwise, the style sheet is queried for Pygments style
89 information.
89 information.
90 """)
90 """)
91
91
92 # Prompts.
92 # Prompts.
93 in_prompt = Unicode(default_in_prompt, config=True)
93 in_prompt = Unicode(default_in_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
98
98
99 # FrontendWidget protected class variables.
99 # FrontendWidget protected class variables.
100 _input_splitter_class = IPythonInputSplitter
100 _input_splitter_class = IPythonInputSplitter
101 _prompt_transformer = IPythonInputSplitter(transforms=[ipy_prompt()])
101 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
102 logical_line_transforms=[],
103 python_line_transforms=[],
104 )
102
105
103 # IPythonWidget protected class variables.
106 # IPythonWidget protected class variables.
104 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
107 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
105 _payload_source_edit = zmq_shell_source + '.edit_magic'
108 _payload_source_edit = zmq_shell_source + '.edit_magic'
106 _payload_source_exit = zmq_shell_source + '.ask_exit'
109 _payload_source_exit = zmq_shell_source + '.ask_exit'
107 _payload_source_next_input = zmq_shell_source + '.set_next_input'
110 _payload_source_next_input = zmq_shell_source + '.set_next_input'
108 _payload_source_page = 'IPython.kernel.zmq.page.page'
111 _payload_source_page = 'IPython.kernel.zmq.page.page'
109 _retrying_history_request = False
112 _retrying_history_request = False
110
113
111 #---------------------------------------------------------------------------
114 #---------------------------------------------------------------------------
112 # 'object' interface
115 # 'object' interface
113 #---------------------------------------------------------------------------
116 #---------------------------------------------------------------------------
114
117
115 def __init__(self, *args, **kw):
118 def __init__(self, *args, **kw):
116 super(IPythonWidget, self).__init__(*args, **kw)
119 super(IPythonWidget, self).__init__(*args, **kw)
117
120
118 # IPythonWidget protected variables.
121 # IPythonWidget protected variables.
119 self._payload_handlers = {
122 self._payload_handlers = {
120 self._payload_source_edit : self._handle_payload_edit,
123 self._payload_source_edit : self._handle_payload_edit,
121 self._payload_source_exit : self._handle_payload_exit,
124 self._payload_source_exit : self._handle_payload_exit,
122 self._payload_source_page : self._handle_payload_page,
125 self._payload_source_page : self._handle_payload_page,
123 self._payload_source_next_input : self._handle_payload_next_input }
126 self._payload_source_next_input : self._handle_payload_next_input }
124 self._previous_prompt_obj = None
127 self._previous_prompt_obj = None
125 self._keep_kernel_on_exit = None
128 self._keep_kernel_on_exit = None
126
129
127 # Initialize widget styling.
130 # Initialize widget styling.
128 if self.style_sheet:
131 if self.style_sheet:
129 self._style_sheet_changed()
132 self._style_sheet_changed()
130 self._syntax_style_changed()
133 self._syntax_style_changed()
131 else:
134 else:
132 self.set_default_style()
135 self.set_default_style()
133
136
134 #---------------------------------------------------------------------------
137 #---------------------------------------------------------------------------
135 # 'BaseFrontendMixin' abstract interface
138 # 'BaseFrontendMixin' abstract interface
136 #---------------------------------------------------------------------------
139 #---------------------------------------------------------------------------
137
140
138 def _handle_complete_reply(self, rep):
141 def _handle_complete_reply(self, rep):
139 """ Reimplemented to support IPython's improved completion machinery.
142 """ Reimplemented to support IPython's improved completion machinery.
140 """
143 """
141 self.log.debug("complete: %s", rep.get('content', ''))
144 self.log.debug("complete: %s", rep.get('content', ''))
142 cursor = self._get_cursor()
145 cursor = self._get_cursor()
143 info = self._request_info.get('complete')
146 info = self._request_info.get('complete')
144 if info and info.id == rep['parent_header']['msg_id'] and \
147 if info and info.id == rep['parent_header']['msg_id'] and \
145 info.pos == cursor.position():
148 info.pos == cursor.position():
146 matches = rep['content']['matches']
149 matches = rep['content']['matches']
147 text = rep['content']['matched_text']
150 text = rep['content']['matched_text']
148 offset = len(text)
151 offset = len(text)
149
152
150 # Clean up matches with period and path separators if the matched
153 # Clean up matches with period and path separators if the matched
151 # text has not been transformed. This is done by truncating all
154 # text has not been transformed. This is done by truncating all
152 # but the last component and then suitably decreasing the offset
155 # but the last component and then suitably decreasing the offset
153 # between the current cursor position and the start of completion.
156 # between the current cursor position and the start of completion.
154 if len(matches) > 1 and matches[0][:offset] == text:
157 if len(matches) > 1 and matches[0][:offset] == text:
155 parts = re.split(r'[./\\]', text)
158 parts = re.split(r'[./\\]', text)
156 sep_count = len(parts) - 1
159 sep_count = len(parts) - 1
157 if sep_count:
160 if sep_count:
158 chop_length = sum(map(len, parts[:sep_count])) + sep_count
161 chop_length = sum(map(len, parts[:sep_count])) + sep_count
159 matches = [ match[chop_length:] for match in matches ]
162 matches = [ match[chop_length:] for match in matches ]
160 offset -= chop_length
163 offset -= chop_length
161
164
162 # Move the cursor to the start of the match and complete.
165 # Move the cursor to the start of the match and complete.
163 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
166 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
164 self._complete_with_items(cursor, matches)
167 self._complete_with_items(cursor, matches)
165
168
166 def _handle_execute_reply(self, msg):
169 def _handle_execute_reply(self, msg):
167 """ Reimplemented to support prompt requests.
170 """ Reimplemented to support prompt requests.
168 """
171 """
169 msg_id = msg['parent_header'].get('msg_id')
172 msg_id = msg['parent_header'].get('msg_id')
170 info = self._request_info['execute'].get(msg_id)
173 info = self._request_info['execute'].get(msg_id)
171 if info and info.kind == 'prompt':
174 if info and info.kind == 'prompt':
172 number = msg['content']['execution_count'] + 1
175 number = msg['content']['execution_count'] + 1
173 self._show_interpreter_prompt(number)
176 self._show_interpreter_prompt(number)
174 self._request_info['execute'].pop(msg_id)
177 self._request_info['execute'].pop(msg_id)
175 else:
178 else:
176 super(IPythonWidget, self)._handle_execute_reply(msg)
179 super(IPythonWidget, self)._handle_execute_reply(msg)
177
180
178 def _handle_history_reply(self, msg):
181 def _handle_history_reply(self, msg):
179 """ Implemented to handle history tail replies, which are only supported
182 """ Implemented to handle history tail replies, which are only supported
180 by the IPython kernel.
183 by the IPython kernel.
181 """
184 """
182 content = msg['content']
185 content = msg['content']
183 if 'history' not in content:
186 if 'history' not in content:
184 self.log.error("History request failed: %r"%content)
187 self.log.error("History request failed: %r"%content)
185 if content.get('status', '') == 'aborted' and \
188 if content.get('status', '') == 'aborted' and \
186 not self._retrying_history_request:
189 not self._retrying_history_request:
187 # a *different* action caused this request to be aborted, so
190 # a *different* action caused this request to be aborted, so
188 # we should try again.
191 # we should try again.
189 self.log.error("Retrying aborted history request")
192 self.log.error("Retrying aborted history request")
190 # prevent multiple retries of aborted requests:
193 # prevent multiple retries of aborted requests:
191 self._retrying_history_request = True
194 self._retrying_history_request = True
192 # wait out the kernel's queue flush, which is currently timed at 0.1s
195 # wait out the kernel's queue flush, which is currently timed at 0.1s
193 time.sleep(0.25)
196 time.sleep(0.25)
194 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
197 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
195 else:
198 else:
196 self._retrying_history_request = False
199 self._retrying_history_request = False
197 return
200 return
198 # reset retry flag
201 # reset retry flag
199 self._retrying_history_request = False
202 self._retrying_history_request = False
200 history_items = content['history']
203 history_items = content['history']
201 self.log.debug("Received history reply with %i entries", len(history_items))
204 self.log.debug("Received history reply with %i entries", len(history_items))
202 items = []
205 items = []
203 last_cell = u""
206 last_cell = u""
204 for _, _, cell in history_items:
207 for _, _, cell in history_items:
205 cell = cell.rstrip()
208 cell = cell.rstrip()
206 if cell != last_cell:
209 if cell != last_cell:
207 items.append(cell)
210 items.append(cell)
208 last_cell = cell
211 last_cell = cell
209 self._set_history(items)
212 self._set_history(items)
210
213
211 def _handle_pyout(self, msg):
214 def _handle_pyout(self, msg):
212 """ Reimplemented for IPython-style "display hook".
215 """ Reimplemented for IPython-style "display hook".
213 """
216 """
214 self.log.debug("pyout: %s", msg.get('content', ''))
217 self.log.debug("pyout: %s", msg.get('content', ''))
215 if not self._hidden and self._is_from_this_session(msg):
218 if not self._hidden and self._is_from_this_session(msg):
216 content = msg['content']
219 content = msg['content']
217 prompt_number = content.get('execution_count', 0)
220 prompt_number = content.get('execution_count', 0)
218 data = content['data']
221 data = content['data']
219 if 'text/html' in data:
222 if 'text/html' in data:
220 self._append_plain_text(self.output_sep, True)
223 self._append_plain_text(self.output_sep, True)
221 self._append_html(self._make_out_prompt(prompt_number), True)
224 self._append_html(self._make_out_prompt(prompt_number), True)
222 html = data['text/html']
225 html = data['text/html']
223 self._append_plain_text('\n', True)
226 self._append_plain_text('\n', True)
224 self._append_html(html + self.output_sep2, True)
227 self._append_html(html + self.output_sep2, True)
225 elif 'text/plain' in data:
228 elif 'text/plain' in data:
226 self._append_plain_text(self.output_sep, True)
229 self._append_plain_text(self.output_sep, True)
227 self._append_html(self._make_out_prompt(prompt_number), True)
230 self._append_html(self._make_out_prompt(prompt_number), True)
228 text = data['text/plain']
231 text = data['text/plain']
229 # If the repr is multiline, make sure we start on a new line,
232 # If the repr is multiline, make sure we start on a new line,
230 # so that its lines are aligned.
233 # so that its lines are aligned.
231 if "\n" in text and not self.output_sep.endswith("\n"):
234 if "\n" in text and not self.output_sep.endswith("\n"):
232 self._append_plain_text('\n', True)
235 self._append_plain_text('\n', True)
233 self._append_plain_text(text + self.output_sep2, True)
236 self._append_plain_text(text + self.output_sep2, True)
234
237
235 def _handle_display_data(self, msg):
238 def _handle_display_data(self, msg):
236 """ The base handler for the ``display_data`` message.
239 """ The base handler for the ``display_data`` message.
237 """
240 """
238 self.log.debug("display: %s", msg.get('content', ''))
241 self.log.debug("display: %s", msg.get('content', ''))
239 # For now, we don't display data from other frontends, but we
242 # For now, we don't display data from other frontends, but we
240 # eventually will as this allows all frontends to monitor the display
243 # eventually will as this allows all frontends to monitor the display
241 # data. But we need to figure out how to handle this in the GUI.
244 # data. But we need to figure out how to handle this in the GUI.
242 if not self._hidden and self._is_from_this_session(msg):
245 if not self._hidden and self._is_from_this_session(msg):
243 source = msg['content']['source']
246 source = msg['content']['source']
244 data = msg['content']['data']
247 data = msg['content']['data']
245 metadata = msg['content']['metadata']
248 metadata = msg['content']['metadata']
246 # In the regular IPythonWidget, we simply print the plain text
249 # In the regular IPythonWidget, we simply print the plain text
247 # representation.
250 # representation.
248 if 'text/html' in data:
251 if 'text/html' in data:
249 html = data['text/html']
252 html = data['text/html']
250 self._append_html(html, True)
253 self._append_html(html, True)
251 elif 'text/plain' in data:
254 elif 'text/plain' in data:
252 text = data['text/plain']
255 text = data['text/plain']
253 self._append_plain_text(text, True)
256 self._append_plain_text(text, True)
254 # This newline seems to be needed for text and html output.
257 # This newline seems to be needed for text and html output.
255 self._append_plain_text(u'\n', True)
258 self._append_plain_text(u'\n', True)
256
259
257 def _started_channels(self):
260 def _started_channels(self):
258 """Reimplemented to make a history request and load %guiref."""
261 """Reimplemented to make a history request and load %guiref."""
259 super(IPythonWidget, self)._started_channels()
262 super(IPythonWidget, self)._started_channels()
260 self._load_guiref_magic()
263 self._load_guiref_magic()
261 self.kernel_manager.shell_channel.history(hist_access_type='tail',
264 self.kernel_manager.shell_channel.history(hist_access_type='tail',
262 n=1000)
265 n=1000)
263
266
264 def _started_kernel(self):
267 def _started_kernel(self):
265 """Load %guiref when the kernel starts (if channels are also started).
268 """Load %guiref when the kernel starts (if channels are also started).
266
269
267 Principally triggered by kernel restart.
270 Principally triggered by kernel restart.
268 """
271 """
269 if self.kernel_manager.shell_channel is not None:
272 if self.kernel_manager.shell_channel is not None:
270 self._load_guiref_magic()
273 self._load_guiref_magic()
271
274
272 def _load_guiref_magic(self):
275 def _load_guiref_magic(self):
273 """Load %guiref magic."""
276 """Load %guiref magic."""
274 self.kernel_manager.shell_channel.execute('\n'.join([
277 self.kernel_manager.shell_channel.execute('\n'.join([
275 "try:",
278 "try:",
276 " _usage",
279 " _usage",
277 "except:",
280 "except:",
278 " from IPython.core import usage as _usage",
281 " from IPython.core import usage as _usage",
279 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
282 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
280 " del _usage",
283 " del _usage",
281 ]), silent=True)
284 ]), silent=True)
282
285
283 #---------------------------------------------------------------------------
286 #---------------------------------------------------------------------------
284 # 'ConsoleWidget' public interface
287 # 'ConsoleWidget' public interface
285 #---------------------------------------------------------------------------
288 #---------------------------------------------------------------------------
286
289
287 #---------------------------------------------------------------------------
290 #---------------------------------------------------------------------------
288 # 'FrontendWidget' public interface
291 # 'FrontendWidget' public interface
289 #---------------------------------------------------------------------------
292 #---------------------------------------------------------------------------
290
293
291 def execute_file(self, path, hidden=False):
294 def execute_file(self, path, hidden=False):
292 """ Reimplemented to use the 'run' magic.
295 """ Reimplemented to use the 'run' magic.
293 """
296 """
294 # Use forward slashes on Windows to avoid escaping each separator.
297 # Use forward slashes on Windows to avoid escaping each separator.
295 if sys.platform == 'win32':
298 if sys.platform == 'win32':
296 path = os.path.normpath(path).replace('\\', '/')
299 path = os.path.normpath(path).replace('\\', '/')
297
300
298 # Perhaps we should not be using %run directly, but while we
301 # Perhaps we should not be using %run directly, but while we
299 # are, it is necessary to quote or escape filenames containing spaces
302 # are, it is necessary to quote or escape filenames containing spaces
300 # or quotes.
303 # or quotes.
301
304
302 # In earlier code here, to minimize escaping, we sometimes quoted the
305 # In earlier code here, to minimize escaping, we sometimes quoted the
303 # filename with single quotes. But to do this, this code must be
306 # filename with single quotes. But to do this, this code must be
304 # platform-aware, because run uses shlex rather than python string
307 # platform-aware, because run uses shlex rather than python string
305 # parsing, so that:
308 # parsing, so that:
306 # * In Win: single quotes can be used in the filename without quoting,
309 # * In Win: single quotes can be used in the filename without quoting,
307 # and we cannot use single quotes to quote the filename.
310 # and we cannot use single quotes to quote the filename.
308 # * In *nix: we can escape double quotes in a double quoted filename,
311 # * In *nix: we can escape double quotes in a double quoted filename,
309 # but can't escape single quotes in a single quoted filename.
312 # but can't escape single quotes in a single quoted filename.
310
313
311 # So to keep this code non-platform-specific and simple, we now only
314 # So to keep this code non-platform-specific and simple, we now only
312 # use double quotes to quote filenames, and escape when needed:
315 # use double quotes to quote filenames, and escape when needed:
313 if ' ' in path or "'" in path or '"' in path:
316 if ' ' in path or "'" in path or '"' in path:
314 path = '"%s"' % path.replace('"', '\\"')
317 path = '"%s"' % path.replace('"', '\\"')
315 self.execute('%%run %s' % path, hidden=hidden)
318 self.execute('%%run %s' % path, hidden=hidden)
316
319
317 #---------------------------------------------------------------------------
320 #---------------------------------------------------------------------------
318 # 'FrontendWidget' protected interface
321 # 'FrontendWidget' protected interface
319 #---------------------------------------------------------------------------
322 #---------------------------------------------------------------------------
320
323
321 def _complete(self):
324 def _complete(self):
322 """ Reimplemented to support IPython's improved completion machinery.
325 """ Reimplemented to support IPython's improved completion machinery.
323 """
326 """
324 # We let the kernel split the input line, so we *always* send an empty
327 # We let the kernel split the input line, so we *always* send an empty
325 # text field. Readline-based frontends do get a real text field which
328 # text field. Readline-based frontends do get a real text field which
326 # they can use.
329 # they can use.
327 text = ''
330 text = ''
328
331
329 # Send the completion request to the kernel
332 # Send the completion request to the kernel
330 msg_id = self.kernel_manager.shell_channel.complete(
333 msg_id = self.kernel_manager.shell_channel.complete(
331 text, # text
334 text, # text
332 self._get_input_buffer_cursor_line(), # line
335 self._get_input_buffer_cursor_line(), # line
333 self._get_input_buffer_cursor_column(), # cursor_pos
336 self._get_input_buffer_cursor_column(), # cursor_pos
334 self.input_buffer) # block
337 self.input_buffer) # block
335 pos = self._get_cursor().position()
338 pos = self._get_cursor().position()
336 info = self._CompletionRequest(msg_id, pos)
339 info = self._CompletionRequest(msg_id, pos)
337 self._request_info['complete'] = info
340 self._request_info['complete'] = info
338
341
339 def _process_execute_error(self, msg):
342 def _process_execute_error(self, msg):
340 """ Reimplemented for IPython-style traceback formatting.
343 """ Reimplemented for IPython-style traceback formatting.
341 """
344 """
342 content = msg['content']
345 content = msg['content']
343 traceback = '\n'.join(content['traceback']) + '\n'
346 traceback = '\n'.join(content['traceback']) + '\n'
344 if False:
347 if False:
345 # FIXME: For now, tracebacks come as plain text, so we can't use
348 # FIXME: For now, tracebacks come as plain text, so we can't use
346 # the html renderer yet. Once we refactor ultratb to produce
349 # the html renderer yet. Once we refactor ultratb to produce
347 # properly styled tracebacks, this branch should be the default
350 # properly styled tracebacks, this branch should be the default
348 traceback = traceback.replace(' ', '&nbsp;')
351 traceback = traceback.replace(' ', '&nbsp;')
349 traceback = traceback.replace('\n', '<br/>')
352 traceback = traceback.replace('\n', '<br/>')
350
353
351 ename = content['ename']
354 ename = content['ename']
352 ename_styled = '<span class="error">%s</span>' % ename
355 ename_styled = '<span class="error">%s</span>' % ename
353 traceback = traceback.replace(ename, ename_styled)
356 traceback = traceback.replace(ename, ename_styled)
354
357
355 self._append_html(traceback)
358 self._append_html(traceback)
356 else:
359 else:
357 # This is the fallback for now, using plain text with ansi escapes
360 # This is the fallback for now, using plain text with ansi escapes
358 self._append_plain_text(traceback)
361 self._append_plain_text(traceback)
359
362
360 def _process_execute_payload(self, item):
363 def _process_execute_payload(self, item):
361 """ Reimplemented to dispatch payloads to handler methods.
364 """ Reimplemented to dispatch payloads to handler methods.
362 """
365 """
363 handler = self._payload_handlers.get(item['source'])
366 handler = self._payload_handlers.get(item['source'])
364 if handler is None:
367 if handler is None:
365 # We have no handler for this type of payload, simply ignore it
368 # We have no handler for this type of payload, simply ignore it
366 return False
369 return False
367 else:
370 else:
368 handler(item)
371 handler(item)
369 return True
372 return True
370
373
371 def _show_interpreter_prompt(self, number=None):
374 def _show_interpreter_prompt(self, number=None):
372 """ Reimplemented for IPython-style prompts.
375 """ Reimplemented for IPython-style prompts.
373 """
376 """
374 # If a number was not specified, make a prompt number request.
377 # If a number was not specified, make a prompt number request.
375 if number is None:
378 if number is None:
376 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
379 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
377 info = self._ExecutionRequest(msg_id, 'prompt')
380 info = self._ExecutionRequest(msg_id, 'prompt')
378 self._request_info['execute'][msg_id] = info
381 self._request_info['execute'][msg_id] = info
379 return
382 return
380
383
381 # Show a new prompt and save information about it so that it can be
384 # Show a new prompt and save information about it so that it can be
382 # updated later if the prompt number turns out to be wrong.
385 # updated later if the prompt number turns out to be wrong.
383 self._prompt_sep = self.input_sep
386 self._prompt_sep = self.input_sep
384 self._show_prompt(self._make_in_prompt(number), html=True)
387 self._show_prompt(self._make_in_prompt(number), html=True)
385 block = self._control.document().lastBlock()
388 block = self._control.document().lastBlock()
386 length = len(self._prompt)
389 length = len(self._prompt)
387 self._previous_prompt_obj = self._PromptBlock(block, length, number)
390 self._previous_prompt_obj = self._PromptBlock(block, length, number)
388
391
389 # Update continuation prompt to reflect (possibly) new prompt length.
392 # Update continuation prompt to reflect (possibly) new prompt length.
390 self._set_continuation_prompt(
393 self._set_continuation_prompt(
391 self._make_continuation_prompt(self._prompt), html=True)
394 self._make_continuation_prompt(self._prompt), html=True)
392
395
393 def _show_interpreter_prompt_for_reply(self, msg):
396 def _show_interpreter_prompt_for_reply(self, msg):
394 """ Reimplemented for IPython-style prompts.
397 """ Reimplemented for IPython-style prompts.
395 """
398 """
396 # Update the old prompt number if necessary.
399 # Update the old prompt number if necessary.
397 content = msg['content']
400 content = msg['content']
398 # abort replies do not have any keys:
401 # abort replies do not have any keys:
399 if content['status'] == 'aborted':
402 if content['status'] == 'aborted':
400 if self._previous_prompt_obj:
403 if self._previous_prompt_obj:
401 previous_prompt_number = self._previous_prompt_obj.number
404 previous_prompt_number = self._previous_prompt_obj.number
402 else:
405 else:
403 previous_prompt_number = 0
406 previous_prompt_number = 0
404 else:
407 else:
405 previous_prompt_number = content['execution_count']
408 previous_prompt_number = content['execution_count']
406 if self._previous_prompt_obj and \
409 if self._previous_prompt_obj and \
407 self._previous_prompt_obj.number != previous_prompt_number:
410 self._previous_prompt_obj.number != previous_prompt_number:
408 block = self._previous_prompt_obj.block
411 block = self._previous_prompt_obj.block
409
412
410 # Make sure the prompt block has not been erased.
413 # Make sure the prompt block has not been erased.
411 if block.isValid() and block.text():
414 if block.isValid() and block.text():
412
415
413 # Remove the old prompt and insert a new prompt.
416 # Remove the old prompt and insert a new prompt.
414 cursor = QtGui.QTextCursor(block)
417 cursor = QtGui.QTextCursor(block)
415 cursor.movePosition(QtGui.QTextCursor.Right,
418 cursor.movePosition(QtGui.QTextCursor.Right,
416 QtGui.QTextCursor.KeepAnchor,
419 QtGui.QTextCursor.KeepAnchor,
417 self._previous_prompt_obj.length)
420 self._previous_prompt_obj.length)
418 prompt = self._make_in_prompt(previous_prompt_number)
421 prompt = self._make_in_prompt(previous_prompt_number)
419 self._prompt = self._insert_html_fetching_plain_text(
422 self._prompt = self._insert_html_fetching_plain_text(
420 cursor, prompt)
423 cursor, prompt)
421
424
422 # When the HTML is inserted, Qt blows away the syntax
425 # When the HTML is inserted, Qt blows away the syntax
423 # highlighting for the line, so we need to rehighlight it.
426 # highlighting for the line, so we need to rehighlight it.
424 self._highlighter.rehighlightBlock(cursor.block())
427 self._highlighter.rehighlightBlock(cursor.block())
425
428
426 self._previous_prompt_obj = None
429 self._previous_prompt_obj = None
427
430
428 # Show a new prompt with the kernel's estimated prompt number.
431 # Show a new prompt with the kernel's estimated prompt number.
429 self._show_interpreter_prompt(previous_prompt_number + 1)
432 self._show_interpreter_prompt(previous_prompt_number + 1)
430
433
431 #---------------------------------------------------------------------------
434 #---------------------------------------------------------------------------
432 # 'IPythonWidget' interface
435 # 'IPythonWidget' interface
433 #---------------------------------------------------------------------------
436 #---------------------------------------------------------------------------
434
437
435 def set_default_style(self, colors='lightbg'):
438 def set_default_style(self, colors='lightbg'):
436 """ Sets the widget style to the class defaults.
439 """ Sets the widget style to the class defaults.
437
440
438 Parameters:
441 Parameters:
439 -----------
442 -----------
440 colors : str, optional (default lightbg)
443 colors : str, optional (default lightbg)
441 Whether to use the default IPython light background or dark
444 Whether to use the default IPython light background or dark
442 background or B&W style.
445 background or B&W style.
443 """
446 """
444 colors = colors.lower()
447 colors = colors.lower()
445 if colors=='lightbg':
448 if colors=='lightbg':
446 self.style_sheet = styles.default_light_style_sheet
449 self.style_sheet = styles.default_light_style_sheet
447 self.syntax_style = styles.default_light_syntax_style
450 self.syntax_style = styles.default_light_syntax_style
448 elif colors=='linux':
451 elif colors=='linux':
449 self.style_sheet = styles.default_dark_style_sheet
452 self.style_sheet = styles.default_dark_style_sheet
450 self.syntax_style = styles.default_dark_syntax_style
453 self.syntax_style = styles.default_dark_syntax_style
451 elif colors=='nocolor':
454 elif colors=='nocolor':
452 self.style_sheet = styles.default_bw_style_sheet
455 self.style_sheet = styles.default_bw_style_sheet
453 self.syntax_style = styles.default_bw_syntax_style
456 self.syntax_style = styles.default_bw_syntax_style
454 else:
457 else:
455 raise KeyError("No such color scheme: %s"%colors)
458 raise KeyError("No such color scheme: %s"%colors)
456
459
457 #---------------------------------------------------------------------------
460 #---------------------------------------------------------------------------
458 # 'IPythonWidget' protected interface
461 # 'IPythonWidget' protected interface
459 #---------------------------------------------------------------------------
462 #---------------------------------------------------------------------------
460
463
461 def _edit(self, filename, line=None):
464 def _edit(self, filename, line=None):
462 """ Opens a Python script for editing.
465 """ Opens a Python script for editing.
463
466
464 Parameters:
467 Parameters:
465 -----------
468 -----------
466 filename : str
469 filename : str
467 A path to a local system file.
470 A path to a local system file.
468
471
469 line : int, optional
472 line : int, optional
470 A line of interest in the file.
473 A line of interest in the file.
471 """
474 """
472 if self.custom_edit:
475 if self.custom_edit:
473 self.custom_edit_requested.emit(filename, line)
476 self.custom_edit_requested.emit(filename, line)
474 elif not self.editor:
477 elif not self.editor:
475 self._append_plain_text('No default editor available.\n'
478 self._append_plain_text('No default editor available.\n'
476 'Specify a GUI text editor in the `IPythonWidget.editor` '
479 'Specify a GUI text editor in the `IPythonWidget.editor` '
477 'configurable to enable the %edit magic')
480 'configurable to enable the %edit magic')
478 else:
481 else:
479 try:
482 try:
480 filename = '"%s"' % filename
483 filename = '"%s"' % filename
481 if line and self.editor_line:
484 if line and self.editor_line:
482 command = self.editor_line.format(filename=filename,
485 command = self.editor_line.format(filename=filename,
483 line=line)
486 line=line)
484 else:
487 else:
485 try:
488 try:
486 command = self.editor.format()
489 command = self.editor.format()
487 except KeyError:
490 except KeyError:
488 command = self.editor.format(filename=filename)
491 command = self.editor.format(filename=filename)
489 else:
492 else:
490 command += ' ' + filename
493 command += ' ' + filename
491 except KeyError:
494 except KeyError:
492 self._append_plain_text('Invalid editor command.\n')
495 self._append_plain_text('Invalid editor command.\n')
493 else:
496 else:
494 try:
497 try:
495 Popen(command, shell=True)
498 Popen(command, shell=True)
496 except OSError:
499 except OSError:
497 msg = 'Opening editor with command "%s" failed.\n'
500 msg = 'Opening editor with command "%s" failed.\n'
498 self._append_plain_text(msg % command)
501 self._append_plain_text(msg % command)
499
502
500 def _make_in_prompt(self, number):
503 def _make_in_prompt(self, number):
501 """ Given a prompt number, returns an HTML In prompt.
504 """ Given a prompt number, returns an HTML In prompt.
502 """
505 """
503 try:
506 try:
504 body = self.in_prompt % number
507 body = self.in_prompt % number
505 except TypeError:
508 except TypeError:
506 # allow in_prompt to leave out number, e.g. '>>> '
509 # allow in_prompt to leave out number, e.g. '>>> '
507 body = self.in_prompt
510 body = self.in_prompt
508 return '<span class="in-prompt">%s</span>' % body
511 return '<span class="in-prompt">%s</span>' % body
509
512
510 def _make_continuation_prompt(self, prompt):
513 def _make_continuation_prompt(self, prompt):
511 """ Given a plain text version of an In prompt, returns an HTML
514 """ Given a plain text version of an In prompt, returns an HTML
512 continuation prompt.
515 continuation prompt.
513 """
516 """
514 end_chars = '...: '
517 end_chars = '...: '
515 space_count = len(prompt.lstrip('\n')) - len(end_chars)
518 space_count = len(prompt.lstrip('\n')) - len(end_chars)
516 body = '&nbsp;' * space_count + end_chars
519 body = '&nbsp;' * space_count + end_chars
517 return '<span class="in-prompt">%s</span>' % body
520 return '<span class="in-prompt">%s</span>' % body
518
521
519 def _make_out_prompt(self, number):
522 def _make_out_prompt(self, number):
520 """ Given a prompt number, returns an HTML Out prompt.
523 """ Given a prompt number, returns an HTML Out prompt.
521 """
524 """
522 body = self.out_prompt % number
525 body = self.out_prompt % number
523 return '<span class="out-prompt">%s</span>' % body
526 return '<span class="out-prompt">%s</span>' % body
524
527
525 #------ Payload handlers --------------------------------------------------
528 #------ Payload handlers --------------------------------------------------
526
529
527 # Payload handlers with a generic interface: each takes the opaque payload
530 # Payload handlers with a generic interface: each takes the opaque payload
528 # dict, unpacks it and calls the underlying functions with the necessary
531 # dict, unpacks it and calls the underlying functions with the necessary
529 # arguments.
532 # arguments.
530
533
531 def _handle_payload_edit(self, item):
534 def _handle_payload_edit(self, item):
532 self._edit(item['filename'], item['line_number'])
535 self._edit(item['filename'], item['line_number'])
533
536
534 def _handle_payload_exit(self, item):
537 def _handle_payload_exit(self, item):
535 self._keep_kernel_on_exit = item['keepkernel']
538 self._keep_kernel_on_exit = item['keepkernel']
536 self.exit_requested.emit(self)
539 self.exit_requested.emit(self)
537
540
538 def _handle_payload_next_input(self, item):
541 def _handle_payload_next_input(self, item):
539 self.input_buffer = dedent(item['text'].rstrip())
542 self.input_buffer = dedent(item['text'].rstrip())
540
543
541 def _handle_payload_page(self, item):
544 def _handle_payload_page(self, item):
542 # Since the plain text widget supports only a very small subset of HTML
545 # Since the plain text widget supports only a very small subset of HTML
543 # and we have no control over the HTML source, we only page HTML
546 # and we have no control over the HTML source, we only page HTML
544 # payloads in the rich text widget.
547 # payloads in the rich text widget.
545 if item['html'] and self.kind == 'rich':
548 if item['html'] and self.kind == 'rich':
546 self._page(item['html'], html=True)
549 self._page(item['html'], html=True)
547 else:
550 else:
548 self._page(item['text'], html=False)
551 self._page(item['text'], html=False)
549
552
550 #------ Trait change handlers --------------------------------------------
553 #------ Trait change handlers --------------------------------------------
551
554
552 def _style_sheet_changed(self):
555 def _style_sheet_changed(self):
553 """ Set the style sheets of the underlying widgets.
556 """ Set the style sheets of the underlying widgets.
554 """
557 """
555 self.setStyleSheet(self.style_sheet)
558 self.setStyleSheet(self.style_sheet)
556 if self._control is not None:
559 if self._control is not None:
557 self._control.document().setDefaultStyleSheet(self.style_sheet)
560 self._control.document().setDefaultStyleSheet(self.style_sheet)
558 bg_color = self._control.palette().window().color()
561 bg_color = self._control.palette().window().color()
559 self._ansi_processor.set_background_color(bg_color)
562 self._ansi_processor.set_background_color(bg_color)
560
563
561 if self._page_control is not None:
564 if self._page_control is not None:
562 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
565 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
563
566
564
567
565
568
566 def _syntax_style_changed(self):
569 def _syntax_style_changed(self):
567 """ Set the style for the syntax highlighter.
570 """ Set the style for the syntax highlighter.
568 """
571 """
569 if self._highlighter is None:
572 if self._highlighter is None:
570 # ignore premature calls
573 # ignore premature calls
571 return
574 return
572 if self.syntax_style:
575 if self.syntax_style:
573 self._highlighter.set_style(self.syntax_style)
576 self._highlighter.set_style(self.syntax_style)
574 else:
577 else:
575 self._highlighter.set_style_sheet(self.style_sheet)
578 self._highlighter.set_style_sheet(self.style_sheet)
576
579
577 #------ Trait default initializers -----------------------------------------
580 #------ Trait default initializers -----------------------------------------
578
581
579 def _banner_default(self):
582 def _banner_default(self):
580 from IPython.core.usage import default_gui_banner
583 from IPython.core.usage import default_gui_banner
581 return default_gui_banner
584 return default_gui_banner
General Comments 0
You need to be logged in to leave comments. Login now