##// END OF EJS Templates
Rename input modes of input splitter to 'line' and 'block'....
Fernando Perez -
Show More
@@ -1,858 +1,862 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 codeop
68 import codeop
69 import re
69 import re
70 import sys
70 import sys
71
71
72 # IPython modules
72 # IPython modules
73 from IPython.utils.text import make_quoted_expr
73 from IPython.utils.text import make_quoted_expr
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75 # Globals
75 # Globals
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77
77
78 # The escape sequences that define the syntax transformations IPython will
78 # The escape sequences that define the syntax transformations IPython will
79 # apply to user input. These can NOT be just changed here: many regular
79 # apply to user input. These can NOT be just changed here: many regular
80 # expressions and other parts of the code may use their hardcoded values, and
80 # expressions and other parts of the code may use their hardcoded values, and
81 # for all intents and purposes they constitute the 'IPython syntax', so they
81 # for all intents and purposes they constitute the 'IPython syntax', so they
82 # should be considered fixed.
82 # should be considered fixed.
83
83
84 ESC_SHELL = '!'
84 ESC_SHELL = '!'
85 ESC_SH_CAP = '!!'
85 ESC_SH_CAP = '!!'
86 ESC_HELP = '?'
86 ESC_HELP = '?'
87 ESC_HELP2 = '??'
87 ESC_HELP2 = '??'
88 ESC_MAGIC = '%'
88 ESC_MAGIC = '%'
89 ESC_QUOTE = ','
89 ESC_QUOTE = ','
90 ESC_QUOTE2 = ';'
90 ESC_QUOTE2 = ';'
91 ESC_PAREN = '/'
91 ESC_PAREN = '/'
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(r'^\s+raise|^\s+return|^\s+pass')
103 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
104 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
104 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
105
105
106
106
107 def num_ini_spaces(s):
107 def num_ini_spaces(s):
108 """Return the number of initial spaces in a string.
108 """Return the number of initial spaces in a string.
109
109
110 Note that tabs are counted as a single space. For now, we do *not* support
110 Note that tabs are counted as a single space. For now, we do *not* support
111 mixing of tabs and spaces in the user's input.
111 mixing of tabs and spaces in the user's input.
112
112
113 Parameters
113 Parameters
114 ----------
114 ----------
115 s : string
115 s : string
116
116
117 Returns
117 Returns
118 -------
118 -------
119 n : int
119 n : int
120 """
120 """
121
121
122 ini_spaces = ini_spaces_re.match(s)
122 ini_spaces = ini_spaces_re.match(s)
123 if ini_spaces:
123 if ini_spaces:
124 return ini_spaces.end()
124 return ini_spaces.end()
125 else:
125 else:
126 return 0
126 return 0
127
127
128
128
129 def remove_comments(src):
129 def remove_comments(src):
130 """Remove all comments from input source.
130 """Remove all comments from input source.
131
131
132 Note: comments are NOT recognized inside of strings!
132 Note: comments are NOT recognized inside of strings!
133
133
134 Parameters
134 Parameters
135 ----------
135 ----------
136 src : string
136 src : string
137 A single or multiline input string.
137 A single or multiline input string.
138
138
139 Returns
139 Returns
140 -------
140 -------
141 String with all Python comments removed.
141 String with all Python comments removed.
142 """
142 """
143
143
144 return re.sub('#.*', '', src)
144 return re.sub('#.*', '', src)
145
145
146
146
147 def get_input_encoding():
147 def get_input_encoding():
148 """Return the default standard input encoding.
148 """Return the default standard input encoding.
149
149
150 If sys.stdin has no encoding, 'ascii' is returned."""
150 If sys.stdin has no encoding, 'ascii' is returned."""
151 # There are strange environments for which sys.stdin.encoding is None. We
151 # There are strange environments for which sys.stdin.encoding is None. We
152 # ensure that a valid encoding is returned.
152 # ensure that a valid encoding is returned.
153 encoding = getattr(sys.stdin, 'encoding', None)
153 encoding = getattr(sys.stdin, 'encoding', None)
154 if encoding is None:
154 if encoding is None:
155 encoding = 'ascii'
155 encoding = 'ascii'
156 return encoding
156 return encoding
157
157
158 #-----------------------------------------------------------------------------
158 #-----------------------------------------------------------------------------
159 # Classes and functions for normal Python syntax handling
159 # Classes and functions for normal Python syntax handling
160 #-----------------------------------------------------------------------------
160 #-----------------------------------------------------------------------------
161
161
162 class InputSplitter(object):
162 class InputSplitter(object):
163 """An object that can split Python source input in executable blocks.
163 """An object that can split Python source input in executable blocks.
164
164
165 This object is designed to be used in one of two basic modes:
165 This object is designed to be used in one of two basic modes:
166
166
167 1. By feeding it python source line-by-line, using :meth:`push`. In this
167 1. By feeding it python source line-by-line, using :meth:`push`. In this
168 mode, it will return on each push whether the currently pushed code
168 mode, it will return on each push whether the currently pushed code
169 could be executed already. In addition, it provides a method called
169 could be executed already. In addition, it provides a method called
170 :meth:`push_accepts_more` that can be used to query whether more input
170 :meth:`push_accepts_more` that can be used to query whether more input
171 can be pushed into a single interactive block.
171 can be pushed into a single interactive block.
172
172
173 2. By calling :meth:`split_blocks` with a single, multiline Python string,
173 2. By calling :meth:`split_blocks` with a single, multiline Python string,
174 that is then split into blocks each of which can be executed
174 that is then split into blocks each of which can be executed
175 interactively as a single statement.
175 interactively as a single statement.
176
176
177 This is a simple example of how an interactive terminal-based client can use
177 This is a simple example of how an interactive terminal-based client can use
178 this tool::
178 this tool::
179
179
180 isp = InputSplitter()
180 isp = InputSplitter()
181 while isp.push_accepts_more():
181 while isp.push_accepts_more():
182 indent = ' '*isp.indent_spaces
182 indent = ' '*isp.indent_spaces
183 prompt = '>>> ' + indent
183 prompt = '>>> ' + indent
184 line = indent + raw_input(prompt)
184 line = indent + raw_input(prompt)
185 isp.push(line)
185 isp.push(line)
186 print 'Input source was:\n', isp.source_reset(),
186 print 'Input source was:\n', isp.source_reset(),
187 """
187 """
188 # Number of spaces of indentation computed from input that has been pushed
188 # Number of spaces of indentation computed from input that has been pushed
189 # so far. This is the attributes callers should query to get the current
189 # so far. This is the attributes callers should query to get the current
190 # indentation level, in order to provide auto-indent facilities.
190 # indentation level, in order to provide auto-indent facilities.
191 indent_spaces = 0
191 indent_spaces = 0
192 # String, indicating the default input encoding. It is computed by default
192 # String, indicating the default input encoding. It is computed by default
193 # at initialization time via get_input_encoding(), but it can be reset by a
193 # at initialization time via get_input_encoding(), but it can be reset by a
194 # client with specific knowledge of the encoding.
194 # client with specific knowledge of the encoding.
195 encoding = ''
195 encoding = ''
196 # String where the current full source input is stored, properly encoded.
196 # String where the current full source input is stored, properly encoded.
197 # Reading this attribute is the normal way of querying the currently pushed
197 # Reading this attribute is the normal way of querying the currently pushed
198 # source code, that has been properly encoded.
198 # source code, that has been properly encoded.
199 source = ''
199 source = ''
200 # Code object corresponding to the current source. It is automatically
200 # Code object corresponding to the current source. It is automatically
201 # synced to the source, so it can be queried at any time to obtain the code
201 # synced to the source, so it can be queried at any time to obtain the code
202 # object; it will be None if the source doesn't compile to valid Python.
202 # object; it will be None if the source doesn't compile to valid Python.
203 code = None
203 code = None
204 # Input mode
204 # Input mode
205 input_mode = 'append'
205 input_mode = 'line'
206
206
207 # Private attributes
207 # Private attributes
208
208
209 # List with lines of input accumulated so far
209 # List with lines of input accumulated so far
210 _buffer = None
210 _buffer = None
211 # Command compiler
211 # Command compiler
212 _compile = None
212 _compile = None
213 # Mark when input has changed indentation all the way back to flush-left
213 # Mark when input has changed indentation all the way back to flush-left
214 _full_dedent = False
214 _full_dedent = False
215 # Boolean indicating whether the current block is complete
215 # Boolean indicating whether the current block is complete
216 _is_complete = None
216 _is_complete = None
217
217
218 def __init__(self, input_mode=None):
218 def __init__(self, input_mode=None):
219 """Create a new InputSplitter instance.
219 """Create a new InputSplitter instance.
220
220
221 Parameters
221 Parameters
222 ----------
222 ----------
223 input_mode : str
223 input_mode : str
224
224
225 One of ['append', 'replace']; default is 'append'. This controls how
225 One of ['line', 'block']; default is 'line'.
226 new inputs are used: in 'append' mode, they are appended to the
227 existing buffer and the whole buffer is compiled; in 'replace' mode,
228 each new input completely replaces all prior inputs. Replace mode is
229 thus equivalent to prepending a full reset() to every push() call.
230
226
231 In practice, line-oriented clients likely want to use 'append' mode
227 The input_mode parameter controls how new inputs are used when fed via
232 while block-oriented ones will want to use 'replace'.
228 the :meth:`push` method:
229
230 - 'line': meant for line-oriented clients, inputs are appended one at a
231 time to the internal buffer and the whole buffer is compiled.
232
233 - 'block': meant for clients that can edit multi-line blocks of text at
234 a time. Each new input new input completely replaces all prior
235 inputs. Block mode is thus equivalent to prepending a full reset()
236 to every push() call.
233 """
237 """
234 self._buffer = []
238 self._buffer = []
235 self._compile = codeop.CommandCompiler()
239 self._compile = codeop.CommandCompiler()
236 self.encoding = get_input_encoding()
240 self.encoding = get_input_encoding()
237 self.input_mode = InputSplitter.input_mode if input_mode is None \
241 self.input_mode = InputSplitter.input_mode if input_mode is None \
238 else input_mode
242 else input_mode
239
243
240 def reset(self):
244 def reset(self):
241 """Reset the input buffer and associated state."""
245 """Reset the input buffer and associated state."""
242 self.indent_spaces = 0
246 self.indent_spaces = 0
243 self._buffer[:] = []
247 self._buffer[:] = []
244 self.source = ''
248 self.source = ''
245 self.code = None
249 self.code = None
246 self._is_complete = False
250 self._is_complete = False
247 self._full_dedent = False
251 self._full_dedent = False
248
252
249 def source_reset(self):
253 def source_reset(self):
250 """Return the input source and perform a full reset.
254 """Return the input source and perform a full reset.
251 """
255 """
252 out = self.source
256 out = self.source
253 self.reset()
257 self.reset()
254 return out
258 return out
255
259
256 def push(self, lines):
260 def push(self, lines):
257 """Push one ore more lines of input.
261 """Push one ore more lines of input.
258
262
259 This stores the given lines and returns a status code indicating
263 This stores the given lines and returns a status code indicating
260 whether the code forms a complete Python block or not.
264 whether the code forms a complete Python block or not.
261
265
262 Any exceptions generated in compilation are swallowed, but if an
266 Any exceptions generated in compilation are swallowed, but if an
263 exception was produced, the method returns True.
267 exception was produced, the method returns True.
264
268
265 Parameters
269 Parameters
266 ----------
270 ----------
267 lines : string
271 lines : string
268 One or more lines of Python input.
272 One or more lines of Python input.
269
273
270 Returns
274 Returns
271 -------
275 -------
272 is_complete : boolean
276 is_complete : boolean
273 True if the current input source (the result of the current input
277 True if the current input source (the result of the current input
274 plus prior inputs) forms a complete Python execution block. Note that
278 plus prior inputs) forms a complete Python execution block. Note that
275 this value is also stored as a private attribute (_is_complete), so it
279 this value is also stored as a private attribute (_is_complete), so it
276 can be queried at any time.
280 can be queried at any time.
277 """
281 """
278 if self.input_mode == 'replace':
282 if self.input_mode == 'block':
279 self.reset()
283 self.reset()
280
284
281 # If the source code has leading blanks, add 'if 1:\n' to it
285 # If the source code has leading blanks, add 'if 1:\n' to it
282 # this allows execution of indented pasted code. It is tempting
286 # this allows execution of indented pasted code. It is tempting
283 # to add '\n' at the end of source to run commands like ' a=1'
287 # to add '\n' at the end of source to run commands like ' a=1'
284 # directly, but this fails for more complicated scenarios
288 # directly, but this fails for more complicated scenarios
285 if not self._buffer and lines[:1] in [' ', '\t']:
289 if not self._buffer and lines[:1] in [' ', '\t']:
286 lines = 'if 1:\n%s' % lines
290 lines = 'if 1:\n%s' % lines
287
291
288 self._store(lines)
292 self._store(lines)
289 source = self.source
293 source = self.source
290
294
291 # Before calling _compile(), reset the code object to None so that if an
295 # Before calling _compile(), reset the code object to None so that if an
292 # exception is raised in compilation, we don't mislead by having
296 # exception is raised in compilation, we don't mislead by having
293 # inconsistent code/source attributes.
297 # inconsistent code/source attributes.
294 self.code, self._is_complete = None, None
298 self.code, self._is_complete = None, None
295
299
296 self._update_indent(lines)
300 self._update_indent(lines)
297 try:
301 try:
298 self.code = self._compile(source)
302 self.code = self._compile(source)
299 # Invalid syntax can produce any of a number of different errors from
303 # Invalid syntax can produce any of a number of different errors from
300 # inside the compiler, so we have to catch them all. Syntax errors
304 # inside the compiler, so we have to catch them all. Syntax errors
301 # immediately produce a 'ready' block, so the invalid Python can be
305 # immediately produce a 'ready' block, so the invalid Python can be
302 # sent to the kernel for evaluation with possible ipython
306 # sent to the kernel for evaluation with possible ipython
303 # special-syntax conversion.
307 # special-syntax conversion.
304 except (SyntaxError, OverflowError, ValueError, TypeError,
308 except (SyntaxError, OverflowError, ValueError, TypeError,
305 MemoryError):
309 MemoryError):
306 self._is_complete = True
310 self._is_complete = True
307 else:
311 else:
308 # Compilation didn't produce any exceptions (though it may not have
312 # Compilation didn't produce any exceptions (though it may not have
309 # given a complete code object)
313 # given a complete code object)
310 self._is_complete = self.code is not None
314 self._is_complete = self.code is not None
311
315
312 return self._is_complete
316 return self._is_complete
313
317
314 def push_accepts_more(self):
318 def push_accepts_more(self):
315 """Return whether a block of interactive input can accept more input.
319 """Return whether a block of interactive input can accept more input.
316
320
317 This method is meant to be used by line-oriented frontends, who need to
321 This method is meant to be used by line-oriented frontends, who need to
318 guess whether a block is complete or not based solely on prior and
322 guess whether a block is complete or not based solely on prior and
319 current input lines. The InputSplitter considers it has a complete
323 current input lines. The InputSplitter considers it has a complete
320 interactive block and will not accept more input only when either a
324 interactive block and will not accept more input only when either a
321 SyntaxError is raised, or *all* of the following are true:
325 SyntaxError is raised, or *all* of the following are true:
322
326
323 1. The input compiles to a complete statement.
327 1. The input compiles to a complete statement.
324
328
325 2. The indentation level is flush-left (because if we are indented,
329 2. The indentation level is flush-left (because if we are indented,
326 like inside a function definition or for loop, we need to keep
330 like inside a function definition or for loop, we need to keep
327 reading new input).
331 reading new input).
328
332
329 3. There is one extra line consisting only of whitespace.
333 3. There is one extra line consisting only of whitespace.
330
334
331 Because of condition #3, this method should be used only by
335 Because of condition #3, this method should be used only by
332 *line-oriented* frontends, since it means that intermediate blank lines
336 *line-oriented* frontends, since it means that intermediate blank lines
333 are not allowed in function definitions (or any other indented block).
337 are not allowed in function definitions (or any other indented block).
334
338
335 Block-oriented frontends that have a separate keyboard event to
339 Block-oriented frontends that have a separate keyboard event to
336 indicate execution should use the :meth:`split_blocks` method instead.
340 indicate execution should use the :meth:`split_blocks` method instead.
337
341
338 If the current input produces a syntax error, this method immediately
342 If the current input produces a syntax error, this method immediately
339 returns False but does *not* raise the syntax error exception, as
343 returns False but does *not* raise the syntax error exception, as
340 typically clients will want to send invalid syntax to an execution
344 typically clients will want to send invalid syntax to an execution
341 backend which might convert the invalid syntax into valid Python via
345 backend which might convert the invalid syntax into valid Python via
342 one of the dynamic IPython mechanisms.
346 one of the dynamic IPython mechanisms.
343 """
347 """
344
348
345 if not self._is_complete:
349 if not self._is_complete:
346 return True
350 return True
347
351
348 if self.indent_spaces==0:
352 if self.indent_spaces==0:
349 return False
353 return False
350
354
351 last_line = self.source.splitlines()[-1]
355 last_line = self.source.splitlines()[-1]
352 return bool(last_line and not last_line.isspace())
356 return bool(last_line and not last_line.isspace())
353
357
354 def split_blocks(self, lines):
358 def split_blocks(self, lines):
355 """Split a multiline string into multiple input blocks.
359 """Split a multiline string into multiple input blocks.
356
360
357 Note: this method starts by performing a full reset().
361 Note: this method starts by performing a full reset().
358
362
359 Parameters
363 Parameters
360 ----------
364 ----------
361 lines : str
365 lines : str
362 A possibly multiline string.
366 A possibly multiline string.
363
367
364 Returns
368 Returns
365 -------
369 -------
366 blocks : list
370 blocks : list
367 A list of strings, each possibly multiline. Each string corresponds
371 A list of strings, each possibly multiline. Each string corresponds
368 to a single block that can be compiled in 'single' mode (unless it
372 to a single block that can be compiled in 'single' mode (unless it
369 has a syntax error)."""
373 has a syntax error)."""
370
374
371 # This code is fairly delicate. If you make any changes here, make
375 # This code is fairly delicate. If you make any changes here, make
372 # absolutely sure that you do run the full test suite and ALL tests
376 # absolutely sure that you do run the full test suite and ALL tests
373 # pass.
377 # pass.
374
378
375 self.reset()
379 self.reset()
376 blocks = []
380 blocks = []
377
381
378 # Reversed copy so we can use pop() efficiently and consume the input
382 # Reversed copy so we can use pop() efficiently and consume the input
379 # as a stack
383 # as a stack
380 lines = lines.splitlines()[::-1]
384 lines = lines.splitlines()[::-1]
381 # Outer loop over all input
385 # Outer loop over all input
382 while lines:
386 while lines:
383 # Inner loop to build each block
387 # Inner loop to build each block
384 while True:
388 while True:
385 # Safety exit from inner loop
389 # Safety exit from inner loop
386 if not lines:
390 if not lines:
387 break
391 break
388 # Grab next line but don't push it yet
392 # Grab next line but don't push it yet
389 next_line = lines.pop()
393 next_line = lines.pop()
390 # Blank/empty lines are pushed as-is
394 # Blank/empty lines are pushed as-is
391 if not next_line or next_line.isspace():
395 if not next_line or next_line.isspace():
392 self.push(next_line)
396 self.push(next_line)
393 continue
397 continue
394
398
395 # Check indentation changes caused by the *next* line
399 # Check indentation changes caused by the *next* line
396 indent_spaces, _full_dedent = self._find_indent(next_line)
400 indent_spaces, _full_dedent = self._find_indent(next_line)
397
401
398 # If the next line causes a dedent, it can be for two differnt
402 # If the next line causes a dedent, it can be for two differnt
399 # reasons: either an explicit de-dent by the user or a
403 # reasons: either an explicit de-dent by the user or a
400 # return/raise/pass statement. These MUST be handled
404 # return/raise/pass statement. These MUST be handled
401 # separately:
405 # separately:
402 #
406 #
403 # 1. the first case is only detected when the actual explicit
407 # 1. the first case is only detected when the actual explicit
404 # dedent happens, and that would be the *first* line of a *new*
408 # dedent happens, and that would be the *first* line of a *new*
405 # block. Thus, we must put the line back into the input buffer
409 # block. Thus, we must put the line back into the input buffer
406 # so that it starts a new block on the next pass.
410 # so that it starts a new block on the next pass.
407 #
411 #
408 # 2. the second case is detected in the line before the actual
412 # 2. the second case is detected in the line before the actual
409 # dedent happens, so , we consume the line and we can break out
413 # dedent happens, so , we consume the line and we can break out
410 # to start a new block.
414 # to start a new block.
411
415
412 # Case 1, explicit dedent causes a break
416 # Case 1, explicit dedent causes a break
413 if _full_dedent and not next_line.startswith(' '):
417 if _full_dedent and not next_line.startswith(' '):
414 lines.append(next_line)
418 lines.append(next_line)
415 break
419 break
416
420
417 # Otherwise any line is pushed
421 # Otherwise any line is pushed
418 self.push(next_line)
422 self.push(next_line)
419
423
420 # Case 2, full dedent with full block ready:
424 # Case 2, full dedent with full block ready:
421 if _full_dedent or \
425 if _full_dedent or \
422 self.indent_spaces==0 and not self.push_accepts_more():
426 self.indent_spaces==0 and not self.push_accepts_more():
423 break
427 break
424 # Form the new block with the current source input
428 # Form the new block with the current source input
425 blocks.append(self.source_reset())
429 blocks.append(self.source_reset())
426
430
427 return blocks
431 return blocks
428
432
429 #------------------------------------------------------------------------
433 #------------------------------------------------------------------------
430 # Private interface
434 # Private interface
431 #------------------------------------------------------------------------
435 #------------------------------------------------------------------------
432
436
433 def _find_indent(self, line):
437 def _find_indent(self, line):
434 """Compute the new indentation level for a single line.
438 """Compute the new indentation level for a single line.
435
439
436 Parameters
440 Parameters
437 ----------
441 ----------
438 line : str
442 line : str
439 A single new line of non-whitespace, non-comment Python input.
443 A single new line of non-whitespace, non-comment Python input.
440
444
441 Returns
445 Returns
442 -------
446 -------
443 indent_spaces : int
447 indent_spaces : int
444 New value for the indent level (it may be equal to self.indent_spaces
448 New value for the indent level (it may be equal to self.indent_spaces
445 if indentation doesn't change.
449 if indentation doesn't change.
446
450
447 full_dedent : boolean
451 full_dedent : boolean
448 Whether the new line causes a full flush-left dedent.
452 Whether the new line causes a full flush-left dedent.
449 """
453 """
450 indent_spaces = self.indent_spaces
454 indent_spaces = self.indent_spaces
451 full_dedent = self._full_dedent
455 full_dedent = self._full_dedent
452
456
453 inisp = num_ini_spaces(line)
457 inisp = num_ini_spaces(line)
454 if inisp < indent_spaces:
458 if inisp < indent_spaces:
455 indent_spaces = inisp
459 indent_spaces = inisp
456 if indent_spaces <= 0:
460 if indent_spaces <= 0:
457 #print 'Full dedent in text',self.source # dbg
461 #print 'Full dedent in text',self.source # dbg
458 full_dedent = True
462 full_dedent = True
459
463
460 if line[-1] == ':':
464 if line[-1] == ':':
461 indent_spaces += 4
465 indent_spaces += 4
462 elif dedent_re.match(line):
466 elif dedent_re.match(line):
463 indent_spaces -= 4
467 indent_spaces -= 4
464 if indent_spaces <= 0:
468 if indent_spaces <= 0:
465 full_dedent = True
469 full_dedent = True
466
470
467 # Safety
471 # Safety
468 if indent_spaces < 0:
472 if indent_spaces < 0:
469 indent_spaces = 0
473 indent_spaces = 0
470 #print 'safety' # dbg
474 #print 'safety' # dbg
471
475
472 return indent_spaces, full_dedent
476 return indent_spaces, full_dedent
473
477
474 def _update_indent(self, lines):
478 def _update_indent(self, lines):
475 for line in remove_comments(lines).splitlines():
479 for line in remove_comments(lines).splitlines():
476 if line and not line.isspace():
480 if line and not line.isspace():
477 self.indent_spaces, self._full_dedent = self._find_indent(line)
481 self.indent_spaces, self._full_dedent = self._find_indent(line)
478
482
479 def _store(self, lines):
483 def _store(self, lines):
480 """Store one or more lines of input.
484 """Store one or more lines of input.
481
485
482 If input lines are not newline-terminated, a newline is automatically
486 If input lines are not newline-terminated, a newline is automatically
483 appended."""
487 appended."""
484
488
485 if lines.endswith('\n'):
489 if lines.endswith('\n'):
486 self._buffer.append(lines)
490 self._buffer.append(lines)
487 else:
491 else:
488 self._buffer.append(lines+'\n')
492 self._buffer.append(lines+'\n')
489 self._set_source()
493 self._set_source()
490
494
491 def _set_source(self):
495 def _set_source(self):
492 self.source = ''.join(self._buffer).encode(self.encoding)
496 self.source = ''.join(self._buffer).encode(self.encoding)
493
497
494
498
495 #-----------------------------------------------------------------------------
499 #-----------------------------------------------------------------------------
496 # Functions and classes for IPython-specific syntactic support
500 # Functions and classes for IPython-specific syntactic support
497 #-----------------------------------------------------------------------------
501 #-----------------------------------------------------------------------------
498
502
499 # RegExp for splitting line contents into pre-char//first word-method//rest.
503 # RegExp for splitting line contents into pre-char//first word-method//rest.
500 # For clarity, each group in on one line.
504 # For clarity, each group in on one line.
501
505
502 line_split = re.compile("""
506 line_split = re.compile("""
503 ^(\s*) # any leading space
507 ^(\s*) # any leading space
504 ([,;/%]|!!?|\?\??) # escape character or characters
508 ([,;/%]|!!?|\?\??) # escape character or characters
505 \s*([\w\.]*) # function/method part (mix of \w and '.')
509 \s*([\w\.]*) # function/method part (mix of \w and '.')
506 (\s+.*$|$) # rest of line
510 (\s+.*$|$) # rest of line
507 """, re.VERBOSE)
511 """, re.VERBOSE)
508
512
509
513
510 def split_user_input(line):
514 def split_user_input(line):
511 """Split user input into early whitespace, esc-char, function part and rest.
515 """Split user input into early whitespace, esc-char, function part and rest.
512
516
513 This is currently handles lines with '=' in them in a very inconsistent
517 This is currently handles lines with '=' in them in a very inconsistent
514 manner.
518 manner.
515
519
516 Examples
520 Examples
517 ========
521 ========
518 >>> split_user_input('x=1')
522 >>> split_user_input('x=1')
519 ('', '', 'x=1', '')
523 ('', '', 'x=1', '')
520 >>> split_user_input('?')
524 >>> split_user_input('?')
521 ('', '?', '', '')
525 ('', '?', '', '')
522 >>> split_user_input('??')
526 >>> split_user_input('??')
523 ('', '??', '', '')
527 ('', '??', '', '')
524 >>> split_user_input(' ?')
528 >>> split_user_input(' ?')
525 (' ', '?', '', '')
529 (' ', '?', '', '')
526 >>> split_user_input(' ??')
530 >>> split_user_input(' ??')
527 (' ', '??', '', '')
531 (' ', '??', '', '')
528 >>> split_user_input('??x')
532 >>> split_user_input('??x')
529 ('', '??', 'x', '')
533 ('', '??', 'x', '')
530 >>> split_user_input('?x=1')
534 >>> split_user_input('?x=1')
531 ('', '', '?x=1', '')
535 ('', '', '?x=1', '')
532 >>> split_user_input('!ls')
536 >>> split_user_input('!ls')
533 ('', '!', 'ls', '')
537 ('', '!', 'ls', '')
534 >>> split_user_input(' !ls')
538 >>> split_user_input(' !ls')
535 (' ', '!', 'ls', '')
539 (' ', '!', 'ls', '')
536 >>> split_user_input('!!ls')
540 >>> split_user_input('!!ls')
537 ('', '!!', 'ls', '')
541 ('', '!!', 'ls', '')
538 >>> split_user_input(' !!ls')
542 >>> split_user_input(' !!ls')
539 (' ', '!!', 'ls', '')
543 (' ', '!!', 'ls', '')
540 >>> split_user_input(',ls')
544 >>> split_user_input(',ls')
541 ('', ',', 'ls', '')
545 ('', ',', 'ls', '')
542 >>> split_user_input(';ls')
546 >>> split_user_input(';ls')
543 ('', ';', 'ls', '')
547 ('', ';', 'ls', '')
544 >>> split_user_input(' ;ls')
548 >>> split_user_input(' ;ls')
545 (' ', ';', 'ls', '')
549 (' ', ';', 'ls', '')
546 >>> split_user_input('f.g(x)')
550 >>> split_user_input('f.g(x)')
547 ('', '', 'f.g(x)', '')
551 ('', '', 'f.g(x)', '')
548 >>> split_user_input('f.g (x)')
552 >>> split_user_input('f.g (x)')
549 ('', '', 'f.g', '(x)')
553 ('', '', 'f.g', '(x)')
550 """
554 """
551 match = line_split.match(line)
555 match = line_split.match(line)
552 if match:
556 if match:
553 lspace, esc, fpart, rest = match.groups()
557 lspace, esc, fpart, rest = match.groups()
554 else:
558 else:
555 # print "match failed for line '%s'" % line
559 # print "match failed for line '%s'" % line
556 try:
560 try:
557 fpart, rest = line.split(None, 1)
561 fpart, rest = line.split(None, 1)
558 except ValueError:
562 except ValueError:
559 # print "split failed for line '%s'" % line
563 # print "split failed for line '%s'" % line
560 fpart, rest = line,''
564 fpart, rest = line,''
561 lspace = re.match('^(\s*)(.*)', line).groups()[0]
565 lspace = re.match('^(\s*)(.*)', line).groups()[0]
562 esc = ''
566 esc = ''
563
567
564 # fpart has to be a valid python identifier, so it better be only pure
568 # fpart has to be a valid python identifier, so it better be only pure
565 # ascii, no unicode:
569 # ascii, no unicode:
566 try:
570 try:
567 fpart = fpart.encode('ascii')
571 fpart = fpart.encode('ascii')
568 except UnicodeEncodeError:
572 except UnicodeEncodeError:
569 lspace = unicode(lspace)
573 lspace = unicode(lspace)
570 rest = fpart + u' ' + rest
574 rest = fpart + u' ' + rest
571 fpart = u''
575 fpart = u''
572
576
573 #print 'line:<%s>' % line # dbg
577 #print 'line:<%s>' % line # dbg
574 #print 'esc <%s> fpart <%s> rest <%s>' % (esc,fpart.strip(),rest) # dbg
578 #print 'esc <%s> fpart <%s> rest <%s>' % (esc,fpart.strip(),rest) # dbg
575 return lspace, esc, fpart.strip(), rest.lstrip()
579 return lspace, esc, fpart.strip(), rest.lstrip()
576
580
577
581
578 # The escaped translators ALL receive a line where their own escape has been
582 # The escaped translators ALL receive a line where their own escape has been
579 # stripped. Only '?' is valid at the end of the line, all others can only be
583 # stripped. Only '?' is valid at the end of the line, all others can only be
580 # placed at the start.
584 # placed at the start.
581
585
582 class LineInfo(object):
586 class LineInfo(object):
583 """A single line of input and associated info.
587 """A single line of input and associated info.
584
588
585 This is a utility class that mostly wraps the output of
589 This is a utility class that mostly wraps the output of
586 :func:`split_user_input` into a convenient object to be passed around
590 :func:`split_user_input` into a convenient object to be passed around
587 during input transformations.
591 during input transformations.
588
592
589 Includes the following as properties:
593 Includes the following as properties:
590
594
591 line
595 line
592 The original, raw line
596 The original, raw line
593
597
594 lspace
598 lspace
595 Any early whitespace before actual text starts.
599 Any early whitespace before actual text starts.
596
600
597 esc
601 esc
598 The initial esc character (or characters, for double-char escapes like
602 The initial esc character (or characters, for double-char escapes like
599 '??' or '!!').
603 '??' or '!!').
600
604
601 fpart
605 fpart
602 The 'function part', which is basically the maximal initial sequence
606 The 'function part', which is basically the maximal initial sequence
603 of valid python identifiers and the '.' character. This is what is
607 of valid python identifiers and the '.' character. This is what is
604 checked for alias and magic transformations, used for auto-calling,
608 checked for alias and magic transformations, used for auto-calling,
605 etc.
609 etc.
606
610
607 rest
611 rest
608 Everything else on the line.
612 Everything else on the line.
609 """
613 """
610 def __init__(self, line):
614 def __init__(self, line):
611 self.line = line
615 self.line = line
612 self.lspace, self.esc, self.fpart, self.rest = \
616 self.lspace, self.esc, self.fpart, self.rest = \
613 split_user_input(line)
617 split_user_input(line)
614
618
615 def __str__(self):
619 def __str__(self):
616 return "LineInfo [%s|%s|%s|%s]" % (self.lspace, self.esc,
620 return "LineInfo [%s|%s|%s|%s]" % (self.lspace, self.esc,
617 self.fpart, self.rest)
621 self.fpart, self.rest)
618
622
619
623
620 # Transformations of the special syntaxes that don't rely on an explicit escape
624 # Transformations of the special syntaxes that don't rely on an explicit escape
621 # character but instead on patterns on the input line
625 # character but instead on patterns on the input line
622
626
623 # The core transformations are implemented as standalone functions that can be
627 # The core transformations are implemented as standalone functions that can be
624 # tested and validated in isolation. Each of these uses a regexp, we
628 # tested and validated in isolation. Each of these uses a regexp, we
625 # pre-compile these and keep them close to each function definition for clarity
629 # pre-compile these and keep them close to each function definition for clarity
626
630
627 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
631 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
628 r'\s*=\s*!\s*(?P<cmd>.*)')
632 r'\s*=\s*!\s*(?P<cmd>.*)')
629
633
630 def transform_assign_system(line):
634 def transform_assign_system(line):
631 """Handle the `files = !ls` syntax."""
635 """Handle the `files = !ls` syntax."""
632 # FIXME: This transforms the line to use %sc, but we've listed that magic
636 # FIXME: This transforms the line to use %sc, but we've listed that magic
633 # as deprecated. We should then implement this functionality in a
637 # as deprecated. We should then implement this functionality in a
634 # standalone api that we can transform to, without going through a
638 # standalone api that we can transform to, without going through a
635 # deprecated magic.
639 # deprecated magic.
636 m = _assign_system_re.match(line)
640 m = _assign_system_re.match(line)
637 if m is not None:
641 if m is not None:
638 cmd = m.group('cmd')
642 cmd = m.group('cmd')
639 lhs = m.group('lhs')
643 lhs = m.group('lhs')
640 expr = make_quoted_expr("sc -l = %s" % cmd)
644 expr = make_quoted_expr("sc -l = %s" % cmd)
641 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
645 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
642 return new_line
646 return new_line
643 return line
647 return line
644
648
645
649
646 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
650 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
647 r'\s*=\s*%\s*(?P<cmd>.*)')
651 r'\s*=\s*%\s*(?P<cmd>.*)')
648
652
649 def transform_assign_magic(line):
653 def transform_assign_magic(line):
650 """Handle the `a = %who` syntax."""
654 """Handle the `a = %who` syntax."""
651 m = _assign_magic_re.match(line)
655 m = _assign_magic_re.match(line)
652 if m is not None:
656 if m is not None:
653 cmd = m.group('cmd')
657 cmd = m.group('cmd')
654 lhs = m.group('lhs')
658 lhs = m.group('lhs')
655 expr = make_quoted_expr(cmd)
659 expr = make_quoted_expr(cmd)
656 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
660 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
657 return new_line
661 return new_line
658 return line
662 return line
659
663
660
664
661 _classic_prompt_re = re.compile(r'^([ \t]*>>> |^[ \t]*\.\.\. )')
665 _classic_prompt_re = re.compile(r'^([ \t]*>>> |^[ \t]*\.\.\. )')
662
666
663 def transform_classic_prompt(line):
667 def transform_classic_prompt(line):
664 """Handle inputs that start with '>>> ' syntax."""
668 """Handle inputs that start with '>>> ' syntax."""
665
669
666 if not line or line.isspace():
670 if not line or line.isspace():
667 return line
671 return line
668 m = _classic_prompt_re.match(line)
672 m = _classic_prompt_re.match(line)
669 if m:
673 if m:
670 return line[len(m.group(0)):]
674 return line[len(m.group(0)):]
671 else:
675 else:
672 return line
676 return line
673
677
674
678
675 _ipy_prompt_re = re.compile(r'^([ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )')
679 _ipy_prompt_re = re.compile(r'^([ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )')
676
680
677 def transform_ipy_prompt(line):
681 def transform_ipy_prompt(line):
678 """Handle inputs that start classic IPython prompt syntax."""
682 """Handle inputs that start classic IPython prompt syntax."""
679
683
680 if not line or line.isspace():
684 if not line or line.isspace():
681 return line
685 return line
682 #print 'LINE: %r' % line # dbg
686 #print 'LINE: %r' % line # dbg
683 m = _ipy_prompt_re.match(line)
687 m = _ipy_prompt_re.match(line)
684 if m:
688 if m:
685 #print 'MATCH! %r -> %r' % (line, line[len(m.group(0)):]) # dbg
689 #print 'MATCH! %r -> %r' % (line, line[len(m.group(0)):]) # dbg
686 return line[len(m.group(0)):]
690 return line[len(m.group(0)):]
687 else:
691 else:
688 return line
692 return line
689
693
690
694
691 class EscapedTransformer(object):
695 class EscapedTransformer(object):
692 """Class to transform lines that are explicitly escaped out."""
696 """Class to transform lines that are explicitly escaped out."""
693
697
694 def __init__(self):
698 def __init__(self):
695 tr = { ESC_SHELL : self._tr_system,
699 tr = { ESC_SHELL : self._tr_system,
696 ESC_SH_CAP : self._tr_system2,
700 ESC_SH_CAP : self._tr_system2,
697 ESC_HELP : self._tr_help,
701 ESC_HELP : self._tr_help,
698 ESC_HELP2 : self._tr_help,
702 ESC_HELP2 : self._tr_help,
699 ESC_MAGIC : self._tr_magic,
703 ESC_MAGIC : self._tr_magic,
700 ESC_QUOTE : self._tr_quote,
704 ESC_QUOTE : self._tr_quote,
701 ESC_QUOTE2 : self._tr_quote2,
705 ESC_QUOTE2 : self._tr_quote2,
702 ESC_PAREN : self._tr_paren }
706 ESC_PAREN : self._tr_paren }
703 self.tr = tr
707 self.tr = tr
704
708
705 # Support for syntax transformations that use explicit escapes typed by the
709 # Support for syntax transformations that use explicit escapes typed by the
706 # user at the beginning of a line
710 # user at the beginning of a line
707 @staticmethod
711 @staticmethod
708 def _tr_system(line_info):
712 def _tr_system(line_info):
709 "Translate lines escaped with: !"
713 "Translate lines escaped with: !"
710 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
714 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
711 return '%sget_ipython().system(%s)' % (line_info.lspace,
715 return '%sget_ipython().system(%s)' % (line_info.lspace,
712 make_quoted_expr(cmd))
716 make_quoted_expr(cmd))
713
717
714 @staticmethod
718 @staticmethod
715 def _tr_system2(line_info):
719 def _tr_system2(line_info):
716 "Translate lines escaped with: !!"
720 "Translate lines escaped with: !!"
717 cmd = line_info.line.lstrip()[2:]
721 cmd = line_info.line.lstrip()[2:]
718 return '%sget_ipython().getoutput(%s)' % (line_info.lspace,
722 return '%sget_ipython().getoutput(%s)' % (line_info.lspace,
719 make_quoted_expr(cmd))
723 make_quoted_expr(cmd))
720
724
721 @staticmethod
725 @staticmethod
722 def _tr_help(line_info):
726 def _tr_help(line_info):
723 "Translate lines escaped with: ?/??"
727 "Translate lines escaped with: ?/??"
724 # A naked help line should just fire the intro help screen
728 # A naked help line should just fire the intro help screen
725 if not line_info.line[1:]:
729 if not line_info.line[1:]:
726 return 'get_ipython().show_usage()'
730 return 'get_ipython().show_usage()'
727
731
728 # There may be one or two '?' at the end, move them to the front so that
732 # There may be one or two '?' at the end, move them to the front so that
729 # the rest of the logic can assume escapes are at the start
733 # the rest of the logic can assume escapes are at the start
730 line = line_info.line
734 line = line_info.line
731 if line.endswith('?'):
735 if line.endswith('?'):
732 line = line[-1] + line[:-1]
736 line = line[-1] + line[:-1]
733 if line.endswith('?'):
737 if line.endswith('?'):
734 line = line[-1] + line[:-1]
738 line = line[-1] + line[:-1]
735 line_info = LineInfo(line)
739 line_info = LineInfo(line)
736
740
737 # From here on, simply choose which level of detail to get.
741 # From here on, simply choose which level of detail to get.
738 if line_info.esc == '?':
742 if line_info.esc == '?':
739 pinfo = 'pinfo'
743 pinfo = 'pinfo'
740 elif line_info.esc == '??':
744 elif line_info.esc == '??':
741 pinfo = 'pinfo2'
745 pinfo = 'pinfo2'
742
746
743 tpl = '%sget_ipython().magic("%s %s")'
747 tpl = '%sget_ipython().magic("%s %s")'
744 return tpl % (line_info.lspace, pinfo,
748 return tpl % (line_info.lspace, pinfo,
745 ' '.join([line_info.fpart, line_info.rest]).strip())
749 ' '.join([line_info.fpart, line_info.rest]).strip())
746
750
747 @staticmethod
751 @staticmethod
748 def _tr_magic(line_info):
752 def _tr_magic(line_info):
749 "Translate lines escaped with: %"
753 "Translate lines escaped with: %"
750 tpl = '%sget_ipython().magic(%s)'
754 tpl = '%sget_ipython().magic(%s)'
751 cmd = make_quoted_expr(' '.join([line_info.fpart,
755 cmd = make_quoted_expr(' '.join([line_info.fpart,
752 line_info.rest]).strip())
756 line_info.rest]).strip())
753 return tpl % (line_info.lspace, cmd)
757 return tpl % (line_info.lspace, cmd)
754
758
755 @staticmethod
759 @staticmethod
756 def _tr_quote(line_info):
760 def _tr_quote(line_info):
757 "Translate lines escaped with: ,"
761 "Translate lines escaped with: ,"
758 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
762 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
759 '", "'.join(line_info.rest.split()) )
763 '", "'.join(line_info.rest.split()) )
760
764
761 @staticmethod
765 @staticmethod
762 def _tr_quote2(line_info):
766 def _tr_quote2(line_info):
763 "Translate lines escaped with: ;"
767 "Translate lines escaped with: ;"
764 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
768 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
765 line_info.rest)
769 line_info.rest)
766
770
767 @staticmethod
771 @staticmethod
768 def _tr_paren(line_info):
772 def _tr_paren(line_info):
769 "Translate lines escaped with: /"
773 "Translate lines escaped with: /"
770 return '%s%s(%s)' % (line_info.lspace, line_info.fpart,
774 return '%s%s(%s)' % (line_info.lspace, line_info.fpart,
771 ", ".join(line_info.rest.split()))
775 ", ".join(line_info.rest.split()))
772
776
773 def __call__(self, line):
777 def __call__(self, line):
774 """Class to transform lines that are explicitly escaped out.
778 """Class to transform lines that are explicitly escaped out.
775
779
776 This calls the above _tr_* static methods for the actual line
780 This calls the above _tr_* static methods for the actual line
777 translations."""
781 translations."""
778
782
779 # Empty lines just get returned unmodified
783 # Empty lines just get returned unmodified
780 if not line or line.isspace():
784 if not line or line.isspace():
781 return line
785 return line
782
786
783 # Get line endpoints, where the escapes can be
787 # Get line endpoints, where the escapes can be
784 line_info = LineInfo(line)
788 line_info = LineInfo(line)
785
789
786 # If the escape is not at the start, only '?' needs to be special-cased.
790 # If the escape is not at the start, only '?' needs to be special-cased.
787 # All other escapes are only valid at the start
791 # All other escapes are only valid at the start
788 if not line_info.esc in self.tr:
792 if not line_info.esc in self.tr:
789 if line.endswith(ESC_HELP):
793 if line.endswith(ESC_HELP):
790 return self._tr_help(line_info)
794 return self._tr_help(line_info)
791 else:
795 else:
792 # If we don't recognize the escape, don't modify the line
796 # If we don't recognize the escape, don't modify the line
793 return line
797 return line
794
798
795 return self.tr[line_info.esc](line_info)
799 return self.tr[line_info.esc](line_info)
796
800
797
801
798 # A function-looking object to be used by the rest of the code. The purpose of
802 # A function-looking object to be used by the rest of the code. The purpose of
799 # the class in this case is to organize related functionality, more than to
803 # the class in this case is to organize related functionality, more than to
800 # manage state.
804 # manage state.
801 transform_escaped = EscapedTransformer()
805 transform_escaped = EscapedTransformer()
802
806
803
807
804 class IPythonInputSplitter(InputSplitter):
808 class IPythonInputSplitter(InputSplitter):
805 """An input splitter that recognizes all of IPython's special syntax."""
809 """An input splitter that recognizes all of IPython's special syntax."""
806
810
807 def push(self, lines):
811 def push(self, lines):
808 """Push one or more lines of IPython input.
812 """Push one or more lines of IPython input.
809 """
813 """
810 if not lines:
814 if not lines:
811 return super(IPythonInputSplitter, self).push(lines)
815 return super(IPythonInputSplitter, self).push(lines)
812
816
813 lines_list = lines.splitlines()
817 lines_list = lines.splitlines()
814
818
815 transforms = [transform_escaped, transform_assign_system,
819 transforms = [transform_escaped, transform_assign_system,
816 transform_assign_magic, transform_ipy_prompt,
820 transform_assign_magic, transform_ipy_prompt,
817 transform_classic_prompt]
821 transform_classic_prompt]
818
822
819 # Transform logic
823 # Transform logic
820 #
824 #
821 # We only apply the line transformers to the input if we have either no
825 # We only apply the line transformers to the input if we have either no
822 # input yet, or complete input, or if the last line of the buffer ends
826 # input yet, or complete input, or if the last line of the buffer ends
823 # with ':' (opening an indented block). This prevents the accidental
827 # with ':' (opening an indented block). This prevents the accidental
824 # transformation of escapes inside multiline expressions like
828 # transformation of escapes inside multiline expressions like
825 # triple-quoted strings or parenthesized expressions.
829 # triple-quoted strings or parenthesized expressions.
826 #
830 #
827 # The last heuristic, while ugly, ensures that the first line of an
831 # The last heuristic, while ugly, ensures that the first line of an
828 # indented block is correctly transformed.
832 # indented block is correctly transformed.
829 #
833 #
830 # FIXME: try to find a cleaner approach for this last bit.
834 # FIXME: try to find a cleaner approach for this last bit.
831
835
832 # If we were in 'replace' mode, since we're going to pump the parent
836 # If we were in 'block' mode, since we're going to pump the parent
833 # class by hand line by line, we need to temporarily switch out to
837 # class by hand line by line, we need to temporarily switch out to
834 # 'append' mode, do a single manual reset and then feed the lines one
838 # 'line' mode, do a single manual reset and then feed the lines one
835 # by one. Note that this only matters if the input has more than one
839 # by one. Note that this only matters if the input has more than one
836 # line.
840 # line.
837 changed_input_mode = False
841 changed_input_mode = False
838
842
839 if len(lines_list)>1 and self.input_mode == 'replace':
843 if len(lines_list)>1 and self.input_mode == 'block':
840 self.reset()
844 self.reset()
841 changed_input_mode = True
845 changed_input_mode = True
842 saved_input_mode = 'replace'
846 saved_input_mode = 'block'
843 self.input_mode = 'append'
847 self.input_mode = 'line'
844
848
845 try:
849 try:
846 push = super(IPythonInputSplitter, self).push
850 push = super(IPythonInputSplitter, self).push
847 for line in lines_list:
851 for line in lines_list:
848 if self._is_complete or not self._buffer or \
852 if self._is_complete or not self._buffer or \
849 (self._buffer and self._buffer[-1].rstrip().endswith(':')):
853 (self._buffer and self._buffer[-1].rstrip().endswith(':')):
850 for f in transforms:
854 for f in transforms:
851 line = f(line)
855 line = f(line)
852
856
853 out = push(line)
857 out = push(line)
854 finally:
858 finally:
855 if changed_input_mode:
859 if changed_input_mode:
856 self.input_mode = saved_input_mode
860 self.input_mode = saved_input_mode
857
861
858 return out
862 return out
@@ -1,648 +1,648 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the inputsplitter module.
2 """Tests for the inputsplitter module.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2010 The IPython Development Team
5 # Copyright (C) 2010 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # stdlib
14 # stdlib
15 import unittest
15 import unittest
16 import sys
16 import sys
17
17
18 # Third party
18 # Third party
19 import nose.tools as nt
19 import nose.tools as nt
20
20
21 # Our own
21 # Our own
22 from IPython.core import inputsplitter as isp
22 from IPython.core import inputsplitter as isp
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Semi-complete examples (also used as tests)
25 # Semi-complete examples (also used as tests)
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 # Note: at the bottom, there's a slightly more complete version of this that
28 # Note: at the bottom, there's a slightly more complete version of this that
29 # can be useful during development of code here.
29 # can be useful during development of code here.
30
30
31 def mini_interactive_loop(raw_input):
31 def mini_interactive_loop(raw_input):
32 """Minimal example of the logic of an interactive interpreter loop.
32 """Minimal example of the logic of an interactive interpreter loop.
33
33
34 This serves as an example, and it is used by the test system with a fake
34 This serves as an example, and it is used by the test system with a fake
35 raw_input that simulates interactive input."""
35 raw_input that simulates interactive input."""
36
36
37 from IPython.core.inputsplitter import InputSplitter
37 from IPython.core.inputsplitter import InputSplitter
38
38
39 isp = InputSplitter()
39 isp = InputSplitter()
40 # In practice, this input loop would be wrapped in an outside loop to read
40 # In practice, this input loop would be wrapped in an outside loop to read
41 # input indefinitely, until some exit/quit command was issued. Here we
41 # input indefinitely, until some exit/quit command was issued. Here we
42 # only illustrate the basic inner loop.
42 # only illustrate the basic inner loop.
43 while isp.push_accepts_more():
43 while isp.push_accepts_more():
44 indent = ' '*isp.indent_spaces
44 indent = ' '*isp.indent_spaces
45 prompt = '>>> ' + indent
45 prompt = '>>> ' + indent
46 line = indent + raw_input(prompt)
46 line = indent + raw_input(prompt)
47 isp.push(line)
47 isp.push(line)
48
48
49 # Here we just return input so we can use it in a test suite, but a real
49 # Here we just return input so we can use it in a test suite, but a real
50 # interpreter would instead send it for execution somewhere.
50 # interpreter would instead send it for execution somewhere.
51 src = isp.source_reset()
51 src = isp.source_reset()
52 #print 'Input source was:\n', src # dbg
52 #print 'Input source was:\n', src # dbg
53 return src
53 return src
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Test utilities, just for local use
56 # Test utilities, just for local use
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 def assemble(block):
59 def assemble(block):
60 """Assemble a block into multi-line sub-blocks."""
60 """Assemble a block into multi-line sub-blocks."""
61 return ['\n'.join(sub_block)+'\n' for sub_block in block]
61 return ['\n'.join(sub_block)+'\n' for sub_block in block]
62
62
63
63
64 def pseudo_input(lines):
64 def pseudo_input(lines):
65 """Return a function that acts like raw_input but feeds the input list."""
65 """Return a function that acts like raw_input but feeds the input list."""
66 ilines = iter(lines)
66 ilines = iter(lines)
67 def raw_in(prompt):
67 def raw_in(prompt):
68 try:
68 try:
69 return next(ilines)
69 return next(ilines)
70 except StopIteration:
70 except StopIteration:
71 return ''
71 return ''
72 return raw_in
72 return raw_in
73
73
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75 # Tests
75 # Tests
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77 def test_spaces():
77 def test_spaces():
78 tests = [('', 0),
78 tests = [('', 0),
79 (' ', 1),
79 (' ', 1),
80 ('\n', 0),
80 ('\n', 0),
81 (' \n', 1),
81 (' \n', 1),
82 ('x', 0),
82 ('x', 0),
83 (' x', 1),
83 (' x', 1),
84 (' x',2),
84 (' x',2),
85 (' x',4),
85 (' x',4),
86 # Note: tabs are counted as a single whitespace!
86 # Note: tabs are counted as a single whitespace!
87 ('\tx', 1),
87 ('\tx', 1),
88 ('\t x', 2),
88 ('\t x', 2),
89 ]
89 ]
90
90
91 for s, nsp in tests:
91 for s, nsp in tests:
92 nt.assert_equal(isp.num_ini_spaces(s), nsp)
92 nt.assert_equal(isp.num_ini_spaces(s), nsp)
93
93
94
94
95 def test_remove_comments():
95 def test_remove_comments():
96 tests = [('text', 'text'),
96 tests = [('text', 'text'),
97 ('text # comment', 'text '),
97 ('text # comment', 'text '),
98 ('text # comment\n', 'text \n'),
98 ('text # comment\n', 'text \n'),
99 ('text # comment \n', 'text \n'),
99 ('text # comment \n', 'text \n'),
100 ('line # c \nline\n','line \nline\n'),
100 ('line # c \nline\n','line \nline\n'),
101 ('line # c \nline#c2 \nline\nline #c\n\n',
101 ('line # c \nline#c2 \nline\nline #c\n\n',
102 'line \nline\nline\nline \n\n'),
102 'line \nline\nline\nline \n\n'),
103 ]
103 ]
104
104
105 for inp, out in tests:
105 for inp, out in tests:
106 nt.assert_equal(isp.remove_comments(inp), out)
106 nt.assert_equal(isp.remove_comments(inp), out)
107
107
108
108
109 def test_get_input_encoding():
109 def test_get_input_encoding():
110 encoding = isp.get_input_encoding()
110 encoding = isp.get_input_encoding()
111 nt.assert_true(isinstance(encoding, basestring))
111 nt.assert_true(isinstance(encoding, basestring))
112 # simple-minded check that at least encoding a simple string works with the
112 # simple-minded check that at least encoding a simple string works with the
113 # encoding we got.
113 # encoding we got.
114 nt.assert_equal('test'.encode(encoding), 'test')
114 nt.assert_equal('test'.encode(encoding), 'test')
115
115
116
116
117 class NoInputEncodingTestCase(unittest.TestCase):
117 class NoInputEncodingTestCase(unittest.TestCase):
118 def setUp(self):
118 def setUp(self):
119 self.old_stdin = sys.stdin
119 self.old_stdin = sys.stdin
120 class X: pass
120 class X: pass
121 fake_stdin = X()
121 fake_stdin = X()
122 sys.stdin = fake_stdin
122 sys.stdin = fake_stdin
123
123
124 def test(self):
124 def test(self):
125 # Verify that if sys.stdin has no 'encoding' attribute we do the right
125 # Verify that if sys.stdin has no 'encoding' attribute we do the right
126 # thing
126 # thing
127 enc = isp.get_input_encoding()
127 enc = isp.get_input_encoding()
128 self.assertEqual(enc, 'ascii')
128 self.assertEqual(enc, 'ascii')
129
129
130 def tearDown(self):
130 def tearDown(self):
131 sys.stdin = self.old_stdin
131 sys.stdin = self.old_stdin
132
132
133
133
134 class InputSplitterTestCase(unittest.TestCase):
134 class InputSplitterTestCase(unittest.TestCase):
135 def setUp(self):
135 def setUp(self):
136 self.isp = isp.InputSplitter()
136 self.isp = isp.InputSplitter()
137
137
138 def test_reset(self):
138 def test_reset(self):
139 isp = self.isp
139 isp = self.isp
140 isp.push('x=1')
140 isp.push('x=1')
141 isp.reset()
141 isp.reset()
142 self.assertEqual(isp._buffer, [])
142 self.assertEqual(isp._buffer, [])
143 self.assertEqual(isp.indent_spaces, 0)
143 self.assertEqual(isp.indent_spaces, 0)
144 self.assertEqual(isp.source, '')
144 self.assertEqual(isp.source, '')
145 self.assertEqual(isp.code, None)
145 self.assertEqual(isp.code, None)
146 self.assertEqual(isp._is_complete, False)
146 self.assertEqual(isp._is_complete, False)
147
147
148 def test_source(self):
148 def test_source(self):
149 self.isp._store('1')
149 self.isp._store('1')
150 self.isp._store('2')
150 self.isp._store('2')
151 self.assertEqual(self.isp.source, '1\n2\n')
151 self.assertEqual(self.isp.source, '1\n2\n')
152 self.assertTrue(len(self.isp._buffer)>0)
152 self.assertTrue(len(self.isp._buffer)>0)
153 self.assertEqual(self.isp.source_reset(), '1\n2\n')
153 self.assertEqual(self.isp.source_reset(), '1\n2\n')
154 self.assertEqual(self.isp._buffer, [])
154 self.assertEqual(self.isp._buffer, [])
155 self.assertEqual(self.isp.source, '')
155 self.assertEqual(self.isp.source, '')
156
156
157 def test_indent(self):
157 def test_indent(self):
158 isp = self.isp # shorthand
158 isp = self.isp # shorthand
159 isp.push('x=1')
159 isp.push('x=1')
160 self.assertEqual(isp.indent_spaces, 0)
160 self.assertEqual(isp.indent_spaces, 0)
161 isp.push('if 1:\n x=1')
161 isp.push('if 1:\n x=1')
162 self.assertEqual(isp.indent_spaces, 4)
162 self.assertEqual(isp.indent_spaces, 4)
163 isp.push('y=2\n')
163 isp.push('y=2\n')
164 self.assertEqual(isp.indent_spaces, 0)
164 self.assertEqual(isp.indent_spaces, 0)
165 isp.push('if 1:')
165 isp.push('if 1:')
166 self.assertEqual(isp.indent_spaces, 4)
166 self.assertEqual(isp.indent_spaces, 4)
167 isp.push(' x=1')
167 isp.push(' x=1')
168 self.assertEqual(isp.indent_spaces, 4)
168 self.assertEqual(isp.indent_spaces, 4)
169 # Blank lines shouldn't change the indent level
169 # Blank lines shouldn't change the indent level
170 isp.push(' '*2)
170 isp.push(' '*2)
171 self.assertEqual(isp.indent_spaces, 4)
171 self.assertEqual(isp.indent_spaces, 4)
172
172
173 def test_indent2(self):
173 def test_indent2(self):
174 isp = self.isp
174 isp = self.isp
175 # When a multiline statement contains parens or multiline strings, we
175 # When a multiline statement contains parens or multiline strings, we
176 # shouldn't get confused.
176 # shouldn't get confused.
177 isp.push("if 1:")
177 isp.push("if 1:")
178 isp.push(" x = (1+\n 2)")
178 isp.push(" x = (1+\n 2)")
179 self.assertEqual(isp.indent_spaces, 4)
179 self.assertEqual(isp.indent_spaces, 4)
180
180
181 def test_dedent(self):
181 def test_dedent(self):
182 isp = self.isp # shorthand
182 isp = self.isp # shorthand
183 isp.push('if 1:')
183 isp.push('if 1:')
184 self.assertEqual(isp.indent_spaces, 4)
184 self.assertEqual(isp.indent_spaces, 4)
185 isp.push(' pass')
185 isp.push(' pass')
186 self.assertEqual(isp.indent_spaces, 0)
186 self.assertEqual(isp.indent_spaces, 0)
187
187
188 def test_push(self):
188 def test_push(self):
189 isp = self.isp
189 isp = self.isp
190 self.assertTrue(isp.push('x=1'))
190 self.assertTrue(isp.push('x=1'))
191
191
192 def test_push2(self):
192 def test_push2(self):
193 isp = self.isp
193 isp = self.isp
194 self.assertFalse(isp.push('if 1:'))
194 self.assertFalse(isp.push('if 1:'))
195 for line in [' x=1', '# a comment', ' y=2']:
195 for line in [' x=1', '# a comment', ' y=2']:
196 self.assertTrue(isp.push(line))
196 self.assertTrue(isp.push(line))
197
197
198 def test_push3(self):
198 def test_push3(self):
199 """Test input with leading whitespace"""
199 """Test input with leading whitespace"""
200 isp = self.isp
200 isp = self.isp
201 isp.push(' x=1')
201 isp.push(' x=1')
202 isp.push(' y=2')
202 isp.push(' y=2')
203 self.assertEqual(isp.source, 'if 1:\n x=1\n y=2\n')
203 self.assertEqual(isp.source, 'if 1:\n x=1\n y=2\n')
204
204
205 def test_replace_mode(self):
205 def test_replace_mode(self):
206 isp = self.isp
206 isp = self.isp
207 isp.input_mode = 'replace'
207 isp.input_mode = 'block'
208 isp.push('x=1')
208 isp.push('x=1')
209 self.assertEqual(isp.source, 'x=1\n')
209 self.assertEqual(isp.source, 'x=1\n')
210 isp.push('x=2')
210 isp.push('x=2')
211 self.assertEqual(isp.source, 'x=2\n')
211 self.assertEqual(isp.source, 'x=2\n')
212
212
213 def test_push_accepts_more(self):
213 def test_push_accepts_more(self):
214 isp = self.isp
214 isp = self.isp
215 isp.push('x=1')
215 isp.push('x=1')
216 self.assertFalse(isp.push_accepts_more())
216 self.assertFalse(isp.push_accepts_more())
217
217
218 def test_push_accepts_more2(self):
218 def test_push_accepts_more2(self):
219 isp = self.isp
219 isp = self.isp
220 isp.push('if 1:')
220 isp.push('if 1:')
221 self.assertTrue(isp.push_accepts_more())
221 self.assertTrue(isp.push_accepts_more())
222 isp.push(' x=1')
222 isp.push(' x=1')
223 self.assertTrue(isp.push_accepts_more())
223 self.assertTrue(isp.push_accepts_more())
224 isp.push('')
224 isp.push('')
225 self.assertFalse(isp.push_accepts_more())
225 self.assertFalse(isp.push_accepts_more())
226
226
227 def test_push_accepts_more3(self):
227 def test_push_accepts_more3(self):
228 isp = self.isp
228 isp = self.isp
229 isp.push("x = (2+\n3)")
229 isp.push("x = (2+\n3)")
230 self.assertFalse(isp.push_accepts_more())
230 self.assertFalse(isp.push_accepts_more())
231
231
232 def test_push_accepts_more4(self):
232 def test_push_accepts_more4(self):
233 isp = self.isp
233 isp = self.isp
234 # When a multiline statement contains parens or multiline strings, we
234 # When a multiline statement contains parens or multiline strings, we
235 # shouldn't get confused.
235 # shouldn't get confused.
236 # FIXME: we should be able to better handle de-dents in statements like
236 # FIXME: we should be able to better handle de-dents in statements like
237 # multiline strings and multiline expressions (continued with \ or
237 # multiline strings and multiline expressions (continued with \ or
238 # parens). Right now we aren't handling the indentation tracking quite
238 # parens). Right now we aren't handling the indentation tracking quite
239 # correctly with this, though in practice it may not be too much of a
239 # correctly with this, though in practice it may not be too much of a
240 # problem. We'll need to see.
240 # problem. We'll need to see.
241 isp.push("if 1:")
241 isp.push("if 1:")
242 isp.push(" x = (2+")
242 isp.push(" x = (2+")
243 isp.push(" 3)")
243 isp.push(" 3)")
244 self.assertTrue(isp.push_accepts_more())
244 self.assertTrue(isp.push_accepts_more())
245 isp.push(" y = 3")
245 isp.push(" y = 3")
246 self.assertTrue(isp.push_accepts_more())
246 self.assertTrue(isp.push_accepts_more())
247 isp.push('')
247 isp.push('')
248 self.assertFalse(isp.push_accepts_more())
248 self.assertFalse(isp.push_accepts_more())
249
249
250 def test_syntax_error(self):
250 def test_syntax_error(self):
251 isp = self.isp
251 isp = self.isp
252 # Syntax errors immediately produce a 'ready' block, so the invalid
252 # Syntax errors immediately produce a 'ready' block, so the invalid
253 # Python can be sent to the kernel for evaluation with possible ipython
253 # Python can be sent to the kernel for evaluation with possible ipython
254 # special-syntax conversion.
254 # special-syntax conversion.
255 isp.push('run foo')
255 isp.push('run foo')
256 self.assertFalse(isp.push_accepts_more())
256 self.assertFalse(isp.push_accepts_more())
257
257
258 def check_split(self, block_lines, compile=True):
258 def check_split(self, block_lines, compile=True):
259 blocks = assemble(block_lines)
259 blocks = assemble(block_lines)
260 lines = ''.join(blocks)
260 lines = ''.join(blocks)
261 oblock = self.isp.split_blocks(lines)
261 oblock = self.isp.split_blocks(lines)
262 self.assertEqual(oblock, blocks)
262 self.assertEqual(oblock, blocks)
263 if compile:
263 if compile:
264 for block in blocks:
264 for block in blocks:
265 self.isp._compile(block)
265 self.isp._compile(block)
266
266
267 def test_split(self):
267 def test_split(self):
268 # All blocks of input we want to test in a list. The format for each
268 # All blocks of input we want to test in a list. The format for each
269 # block is a list of lists, with each inner lists consisting of all the
269 # block is a list of lists, with each inner lists consisting of all the
270 # lines (as single-lines) that should make up a sub-block.
270 # lines (as single-lines) that should make up a sub-block.
271
271
272 # Note: do NOT put here sub-blocks that don't compile, as the
272 # Note: do NOT put here sub-blocks that don't compile, as the
273 # check_split() routine makes a final verification pass to check that
273 # check_split() routine makes a final verification pass to check that
274 # each sub_block, as returned by split_blocks(), does compile
274 # each sub_block, as returned by split_blocks(), does compile
275 # correctly.
275 # correctly.
276 all_blocks = [ [['x=1']],
276 all_blocks = [ [['x=1']],
277
277
278 [['x=1'],
278 [['x=1'],
279 ['y=2']],
279 ['y=2']],
280
280
281 [['x=1'],
281 [['x=1'],
282 ['# a comment'],
282 ['# a comment'],
283 ['y=11']],
283 ['y=11']],
284
284
285 [['if 1:',
285 [['if 1:',
286 ' x=1'],
286 ' x=1'],
287 ['y=3']],
287 ['y=3']],
288
288
289 [['def f(x):',
289 [['def f(x):',
290 ' return x'],
290 ' return x'],
291 ['x=1']],
291 ['x=1']],
292
292
293 [['def f(x):',
293 [['def f(x):',
294 ' x+=1',
294 ' x+=1',
295 ' ',
295 ' ',
296 ' return x'],
296 ' return x'],
297 ['x=1']],
297 ['x=1']],
298
298
299 [['def f(x):',
299 [['def f(x):',
300 ' if x>0:',
300 ' if x>0:',
301 ' y=1',
301 ' y=1',
302 ' # a comment',
302 ' # a comment',
303 ' else:',
303 ' else:',
304 ' y=4',
304 ' y=4',
305 ' ',
305 ' ',
306 ' return y'],
306 ' return y'],
307 ['x=1'],
307 ['x=1'],
308 ['if 1:',
308 ['if 1:',
309 ' y=11'] ],
309 ' y=11'] ],
310
310
311 [['for i in range(10):'
311 [['for i in range(10):'
312 ' x=i**2']],
312 ' x=i**2']],
313
313
314 [['for i in range(10):'
314 [['for i in range(10):'
315 ' x=i**2'],
315 ' x=i**2'],
316 ['z = 1']],
316 ['z = 1']],
317 ]
317 ]
318 for block_lines in all_blocks:
318 for block_lines in all_blocks:
319 self.check_split(block_lines)
319 self.check_split(block_lines)
320
320
321 def test_split_syntax_errors(self):
321 def test_split_syntax_errors(self):
322 # Block splitting with invalid syntax
322 # Block splitting with invalid syntax
323 all_blocks = [ [['a syntax error']],
323 all_blocks = [ [['a syntax error']],
324
324
325 [['x=1'],
325 [['x=1'],
326 ['a syntax error']],
326 ['a syntax error']],
327
327
328 [['for i in range(10):'
328 [['for i in range(10):'
329 ' an error']],
329 ' an error']],
330
330
331 ]
331 ]
332 for block_lines in all_blocks:
332 for block_lines in all_blocks:
333 self.check_split(block_lines, compile=False)
333 self.check_split(block_lines, compile=False)
334
334
335
335
336 class InteractiveLoopTestCase(unittest.TestCase):
336 class InteractiveLoopTestCase(unittest.TestCase):
337 """Tests for an interactive loop like a python shell.
337 """Tests for an interactive loop like a python shell.
338 """
338 """
339 def check_ns(self, lines, ns):
339 def check_ns(self, lines, ns):
340 """Validate that the given input lines produce the resulting namespace.
340 """Validate that the given input lines produce the resulting namespace.
341
341
342 Note: the input lines are given exactly as they would be typed in an
342 Note: the input lines are given exactly as they would be typed in an
343 auto-indenting environment, as mini_interactive_loop above already does
343 auto-indenting environment, as mini_interactive_loop above already does
344 auto-indenting and prepends spaces to the input.
344 auto-indenting and prepends spaces to the input.
345 """
345 """
346 src = mini_interactive_loop(pseudo_input(lines))
346 src = mini_interactive_loop(pseudo_input(lines))
347 test_ns = {}
347 test_ns = {}
348 exec src in test_ns
348 exec src in test_ns
349 # We can't check that the provided ns is identical to the test_ns,
349 # We can't check that the provided ns is identical to the test_ns,
350 # because Python fills test_ns with extra keys (copyright, etc). But
350 # because Python fills test_ns with extra keys (copyright, etc). But
351 # we can check that the given dict is *contained* in test_ns
351 # we can check that the given dict is *contained* in test_ns
352 for k,v in ns.items():
352 for k,v in ns.items():
353 self.assertEqual(test_ns[k], v)
353 self.assertEqual(test_ns[k], v)
354
354
355 def test_simple(self):
355 def test_simple(self):
356 self.check_ns(['x=1'], dict(x=1))
356 self.check_ns(['x=1'], dict(x=1))
357
357
358 def test_simple2(self):
358 def test_simple2(self):
359 self.check_ns(['if 1:', 'x=2'], dict(x=2))
359 self.check_ns(['if 1:', 'x=2'], dict(x=2))
360
360
361 def test_xy(self):
361 def test_xy(self):
362 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
362 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
363
363
364 def test_abc(self):
364 def test_abc(self):
365 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
365 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
366
366
367 def test_multi(self):
367 def test_multi(self):
368 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
368 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
369
369
370
370
371 def test_LineInfo():
371 def test_LineInfo():
372 """Simple test for LineInfo construction and str()"""
372 """Simple test for LineInfo construction and str()"""
373 linfo = isp.LineInfo(' %cd /home')
373 linfo = isp.LineInfo(' %cd /home')
374 nt.assert_equals(str(linfo), 'LineInfo [ |%|cd|/home]')
374 nt.assert_equals(str(linfo), 'LineInfo [ |%|cd|/home]')
375
375
376
376
377 def test_split_user_input():
377 def test_split_user_input():
378 """Unicode test - split_user_input already has good doctests"""
378 """Unicode test - split_user_input already has good doctests"""
379 line = u"PΓ©rez Fernando"
379 line = u"PΓ©rez Fernando"
380 parts = isp.split_user_input(line)
380 parts = isp.split_user_input(line)
381 parts_expected = (u'', u'', u'', line)
381 parts_expected = (u'', u'', u'', line)
382 nt.assert_equal(parts, parts_expected)
382 nt.assert_equal(parts, parts_expected)
383
383
384
384
385 # Transformer tests
385 # Transformer tests
386 def transform_checker(tests, func):
386 def transform_checker(tests, func):
387 """Utility to loop over test inputs"""
387 """Utility to loop over test inputs"""
388 for inp, tr in tests:
388 for inp, tr in tests:
389 nt.assert_equals(func(inp), tr)
389 nt.assert_equals(func(inp), tr)
390
390
391 # Data for all the syntax tests in the form of lists of pairs of
391 # Data for all the syntax tests in the form of lists of pairs of
392 # raw/transformed input. We store it here as a global dict so that we can use
392 # raw/transformed input. We store it here as a global dict so that we can use
393 # it both within single-function tests and also to validate the behavior of the
393 # it both within single-function tests and also to validate the behavior of the
394 # larger objects
394 # larger objects
395
395
396 syntax = \
396 syntax = \
397 dict(assign_system =
397 dict(assign_system =
398 [('a =! ls', 'a = get_ipython().magic("sc -l = ls")'),
398 [('a =! ls', 'a = get_ipython().magic("sc -l = ls")'),
399 ('b = !ls', 'b = get_ipython().magic("sc -l = ls")'),
399 ('b = !ls', 'b = get_ipython().magic("sc -l = ls")'),
400 ('x=1', 'x=1'), # normal input is unmodified
400 ('x=1', 'x=1'), # normal input is unmodified
401 (' ',' '), # blank lines are kept intact
401 (' ',' '), # blank lines are kept intact
402 ],
402 ],
403
403
404 assign_magic =
404 assign_magic =
405 [('a =% who', 'a = get_ipython().magic("who")'),
405 [('a =% who', 'a = get_ipython().magic("who")'),
406 ('b = %who', 'b = get_ipython().magic("who")'),
406 ('b = %who', 'b = get_ipython().magic("who")'),
407 ('x=1', 'x=1'), # normal input is unmodified
407 ('x=1', 'x=1'), # normal input is unmodified
408 (' ',' '), # blank lines are kept intact
408 (' ',' '), # blank lines are kept intact
409 ],
409 ],
410
410
411 classic_prompt =
411 classic_prompt =
412 [('>>> x=1', 'x=1'),
412 [('>>> x=1', 'x=1'),
413 ('x=1', 'x=1'), # normal input is unmodified
413 ('x=1', 'x=1'), # normal input is unmodified
414 (' ', ' '), # blank lines are kept intact
414 (' ', ' '), # blank lines are kept intact
415 ('... ', ''), # continuation prompts
415 ('... ', ''), # continuation prompts
416 ],
416 ],
417
417
418 ipy_prompt =
418 ipy_prompt =
419 [('In [1]: x=1', 'x=1'),
419 [('In [1]: x=1', 'x=1'),
420 ('x=1', 'x=1'), # normal input is unmodified
420 ('x=1', 'x=1'), # normal input is unmodified
421 (' ',' '), # blank lines are kept intact
421 (' ',' '), # blank lines are kept intact
422 (' ....: ', ''), # continuation prompts
422 (' ....: ', ''), # continuation prompts
423 ],
423 ],
424
424
425 # Tests for the escape transformer to leave normal code alone
425 # Tests for the escape transformer to leave normal code alone
426 escaped_noesc =
426 escaped_noesc =
427 [ (' ', ' '),
427 [ (' ', ' '),
428 ('x=1', 'x=1'),
428 ('x=1', 'x=1'),
429 ],
429 ],
430
430
431 # System calls
431 # System calls
432 escaped_shell =
432 escaped_shell =
433 [ ('!ls', 'get_ipython().system("ls")'),
433 [ ('!ls', 'get_ipython().system("ls")'),
434 # Double-escape shell, this means to capture the output of the
434 # Double-escape shell, this means to capture the output of the
435 # subprocess and return it
435 # subprocess and return it
436 ('!!ls', 'get_ipython().getoutput("ls")'),
436 ('!!ls', 'get_ipython().getoutput("ls")'),
437 ],
437 ],
438
438
439 # Help/object info
439 # Help/object info
440 escaped_help =
440 escaped_help =
441 [ ('?', 'get_ipython().show_usage()'),
441 [ ('?', 'get_ipython().show_usage()'),
442 ('?x1', 'get_ipython().magic("pinfo x1")'),
442 ('?x1', 'get_ipython().magic("pinfo x1")'),
443 ('??x2', 'get_ipython().magic("pinfo2 x2")'),
443 ('??x2', 'get_ipython().magic("pinfo2 x2")'),
444 ('x3?', 'get_ipython().magic("pinfo x3")'),
444 ('x3?', 'get_ipython().magic("pinfo x3")'),
445 ('x4??', 'get_ipython().magic("pinfo2 x4")'),
445 ('x4??', 'get_ipython().magic("pinfo2 x4")'),
446 ],
446 ],
447
447
448 # Explicit magic calls
448 # Explicit magic calls
449 escaped_magic =
449 escaped_magic =
450 [ ('%cd', 'get_ipython().magic("cd")'),
450 [ ('%cd', 'get_ipython().magic("cd")'),
451 ('%cd /home', 'get_ipython().magic("cd /home")'),
451 ('%cd /home', 'get_ipython().magic("cd /home")'),
452 (' %magic', ' get_ipython().magic("magic")'),
452 (' %magic', ' get_ipython().magic("magic")'),
453 ],
453 ],
454
454
455 # Quoting with separate arguments
455 # Quoting with separate arguments
456 escaped_quote =
456 escaped_quote =
457 [ (',f', 'f("")'),
457 [ (',f', 'f("")'),
458 (',f x', 'f("x")'),
458 (',f x', 'f("x")'),
459 (' ,f y', ' f("y")'),
459 (' ,f y', ' f("y")'),
460 (',f a b', 'f("a", "b")'),
460 (',f a b', 'f("a", "b")'),
461 ],
461 ],
462
462
463 # Quoting with single argument
463 # Quoting with single argument
464 escaped_quote2 =
464 escaped_quote2 =
465 [ (';f', 'f("")'),
465 [ (';f', 'f("")'),
466 (';f x', 'f("x")'),
466 (';f x', 'f("x")'),
467 (' ;f y', ' f("y")'),
467 (' ;f y', ' f("y")'),
468 (';f a b', 'f("a b")'),
468 (';f a b', 'f("a b")'),
469 ],
469 ],
470
470
471 # Simply apply parens
471 # Simply apply parens
472 escaped_paren =
472 escaped_paren =
473 [ ('/f', 'f()'),
473 [ ('/f', 'f()'),
474 ('/f x', 'f(x)'),
474 ('/f x', 'f(x)'),
475 (' /f y', ' f(y)'),
475 (' /f y', ' f(y)'),
476 ('/f a b', 'f(a, b)'),
476 ('/f a b', 'f(a, b)'),
477 ],
477 ],
478
478
479 )
479 )
480
480
481 # multiline syntax examples. Each of these should be a list of lists, with
481 # multiline syntax examples. Each of these should be a list of lists, with
482 # each entry itself having pairs of raw/transformed input. The union (with
482 # each entry itself having pairs of raw/transformed input. The union (with
483 # '\n'.join() of the transformed inputs is what the splitter should produce
483 # '\n'.join() of the transformed inputs is what the splitter should produce
484 # when fed the raw lines one at a time via push.
484 # when fed the raw lines one at a time via push.
485 syntax_ml = \
485 syntax_ml = \
486 dict(classic_prompt =
486 dict(classic_prompt =
487 [ [('>>> for i in range(10):','for i in range(10):'),
487 [ [('>>> for i in range(10):','for i in range(10):'),
488 ('... print i',' print i'),
488 ('... print i',' print i'),
489 ('... ', ''),
489 ('... ', ''),
490 ],
490 ],
491 ],
491 ],
492
492
493 ipy_prompt =
493 ipy_prompt =
494 [ [('In [24]: for i in range(10):','for i in range(10):'),
494 [ [('In [24]: for i in range(10):','for i in range(10):'),
495 (' ....: print i',' print i'),
495 (' ....: print i',' print i'),
496 (' ....: ', ''),
496 (' ....: ', ''),
497 ],
497 ],
498 ],
498 ],
499 )
499 )
500
500
501
501
502 def test_assign_system():
502 def test_assign_system():
503 transform_checker(syntax['assign_system'], isp.transform_assign_system)
503 transform_checker(syntax['assign_system'], isp.transform_assign_system)
504
504
505
505
506 def test_assign_magic():
506 def test_assign_magic():
507 transform_checker(syntax['assign_magic'], isp.transform_assign_magic)
507 transform_checker(syntax['assign_magic'], isp.transform_assign_magic)
508
508
509
509
510 def test_classic_prompt():
510 def test_classic_prompt():
511 transform_checker(syntax['classic_prompt'], isp.transform_classic_prompt)
511 transform_checker(syntax['classic_prompt'], isp.transform_classic_prompt)
512 for example in syntax_ml['classic_prompt']:
512 for example in syntax_ml['classic_prompt']:
513 transform_checker(example, isp.transform_classic_prompt)
513 transform_checker(example, isp.transform_classic_prompt)
514
514
515
515
516 def test_ipy_prompt():
516 def test_ipy_prompt():
517 transform_checker(syntax['ipy_prompt'], isp.transform_ipy_prompt)
517 transform_checker(syntax['ipy_prompt'], isp.transform_ipy_prompt)
518 for example in syntax_ml['ipy_prompt']:
518 for example in syntax_ml['ipy_prompt']:
519 transform_checker(example, isp.transform_ipy_prompt)
519 transform_checker(example, isp.transform_ipy_prompt)
520
520
521
521
522 def test_escaped_noesc():
522 def test_escaped_noesc():
523 transform_checker(syntax['escaped_noesc'], isp.transform_escaped)
523 transform_checker(syntax['escaped_noesc'], isp.transform_escaped)
524
524
525
525
526 def test_escaped_shell():
526 def test_escaped_shell():
527 transform_checker(syntax['escaped_shell'], isp.transform_escaped)
527 transform_checker(syntax['escaped_shell'], isp.transform_escaped)
528
528
529
529
530 def test_escaped_help():
530 def test_escaped_help():
531 transform_checker(syntax['escaped_help'], isp.transform_escaped)
531 transform_checker(syntax['escaped_help'], isp.transform_escaped)
532
532
533
533
534 def test_escaped_magic():
534 def test_escaped_magic():
535 transform_checker(syntax['escaped_magic'], isp.transform_escaped)
535 transform_checker(syntax['escaped_magic'], isp.transform_escaped)
536
536
537
537
538 def test_escaped_quote():
538 def test_escaped_quote():
539 transform_checker(syntax['escaped_quote'], isp.transform_escaped)
539 transform_checker(syntax['escaped_quote'], isp.transform_escaped)
540
540
541
541
542 def test_escaped_quote2():
542 def test_escaped_quote2():
543 transform_checker(syntax['escaped_quote2'], isp.transform_escaped)
543 transform_checker(syntax['escaped_quote2'], isp.transform_escaped)
544
544
545
545
546 def test_escaped_paren():
546 def test_escaped_paren():
547 transform_checker(syntax['escaped_paren'], isp.transform_escaped)
547 transform_checker(syntax['escaped_paren'], isp.transform_escaped)
548
548
549
549
550 class IPythonInputTestCase(InputSplitterTestCase):
550 class IPythonInputTestCase(InputSplitterTestCase):
551 """By just creating a new class whose .isp is a different instance, we
551 """By just creating a new class whose .isp is a different instance, we
552 re-run the same test battery on the new input splitter.
552 re-run the same test battery on the new input splitter.
553
553
554 In addition, this runs the tests over the syntax and syntax_ml dicts that
554 In addition, this runs the tests over the syntax and syntax_ml dicts that
555 were tested by individual functions, as part of the OO interface.
555 were tested by individual functions, as part of the OO interface.
556 """
556 """
557
557
558 def setUp(self):
558 def setUp(self):
559 self.isp = isp.IPythonInputSplitter(input_mode='append')
559 self.isp = isp.IPythonInputSplitter(input_mode='line')
560
560
561 def test_syntax(self):
561 def test_syntax(self):
562 """Call all single-line syntax tests from the main object"""
562 """Call all single-line syntax tests from the main object"""
563 isp = self.isp
563 isp = self.isp
564 for example in syntax.itervalues():
564 for example in syntax.itervalues():
565 for raw, out_t in example:
565 for raw, out_t in example:
566 if raw.startswith(' '):
566 if raw.startswith(' '):
567 continue
567 continue
568
568
569 isp.push(raw)
569 isp.push(raw)
570 out = isp.source_reset().rstrip()
570 out = isp.source_reset().rstrip()
571 self.assertEqual(out, out_t)
571 self.assertEqual(out, out_t)
572
572
573 def test_syntax_multiline(self):
573 def test_syntax_multiline(self):
574 isp = self.isp
574 isp = self.isp
575 for example in syntax_ml.itervalues():
575 for example in syntax_ml.itervalues():
576 out_t_parts = []
576 out_t_parts = []
577 for line_pairs in example:
577 for line_pairs in example:
578 for raw, out_t_part in line_pairs:
578 for raw, out_t_part in line_pairs:
579 isp.push(raw)
579 isp.push(raw)
580 out_t_parts.append(out_t_part)
580 out_t_parts.append(out_t_part)
581
581
582 out = isp.source_reset().rstrip()
582 out = isp.source_reset().rstrip()
583 out_t = '\n'.join(out_t_parts).rstrip()
583 out_t = '\n'.join(out_t_parts).rstrip()
584 self.assertEqual(out, out_t)
584 self.assertEqual(out, out_t)
585
585
586
586
587 class BlockIPythonInputTestCase(IPythonInputTestCase):
587 class BlockIPythonInputTestCase(IPythonInputTestCase):
588
588
589 # Deactivate tests that don't make sense for the block mode
589 # Deactivate tests that don't make sense for the block mode
590 test_push3 = test_split = lambda s: None
590 test_push3 = test_split = lambda s: None
591
591
592 def setUp(self):
592 def setUp(self):
593 self.isp = isp.IPythonInputSplitter(input_mode='replace')
593 self.isp = isp.IPythonInputSplitter(input_mode='block')
594
594
595 def test_syntax_multiline(self):
595 def test_syntax_multiline(self):
596 isp = self.isp
596 isp = self.isp
597 for example in syntax_ml.itervalues():
597 for example in syntax_ml.itervalues():
598 raw_parts = []
598 raw_parts = []
599 out_t_parts = []
599 out_t_parts = []
600 for line_pairs in example:
600 for line_pairs in example:
601 for raw, out_t_part in line_pairs:
601 for raw, out_t_part in line_pairs:
602 raw_parts.append(raw)
602 raw_parts.append(raw)
603 out_t_parts.append(out_t_part)
603 out_t_parts.append(out_t_part)
604
604
605 raw = '\n'.join(raw_parts)
605 raw = '\n'.join(raw_parts)
606 out_t = '\n'.join(out_t_parts)
606 out_t = '\n'.join(out_t_parts)
607
607
608 isp.push(raw)
608 isp.push(raw)
609 out = isp.source_reset()
609 out = isp.source_reset()
610 # Match ignoring trailing whitespace
610 # Match ignoring trailing whitespace
611 self.assertEqual(out.rstrip(), out_t.rstrip())
611 self.assertEqual(out.rstrip(), out_t.rstrip())
612
612
613
613
614 #-----------------------------------------------------------------------------
614 #-----------------------------------------------------------------------------
615 # Main - use as a script, mostly for developer experiments
615 # Main - use as a script, mostly for developer experiments
616 #-----------------------------------------------------------------------------
616 #-----------------------------------------------------------------------------
617
617
618 if __name__ == '__main__':
618 if __name__ == '__main__':
619 # A simple demo for interactive experimentation. This code will not get
619 # A simple demo for interactive experimentation. This code will not get
620 # picked up by any test suite.
620 # picked up by any test suite.
621 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
621 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
622
622
623 # configure here the syntax to use, prompt and whether to autoindent
623 # configure here the syntax to use, prompt and whether to autoindent
624 #isp, start_prompt = InputSplitter(), '>>> '
624 #isp, start_prompt = InputSplitter(), '>>> '
625 isp, start_prompt = IPythonInputSplitter(), 'In> '
625 isp, start_prompt = IPythonInputSplitter(), 'In> '
626
626
627 autoindent = True
627 autoindent = True
628 #autoindent = False
628 #autoindent = False
629
629
630 try:
630 try:
631 while True:
631 while True:
632 prompt = start_prompt
632 prompt = start_prompt
633 while isp.push_accepts_more():
633 while isp.push_accepts_more():
634 indent = ' '*isp.indent_spaces
634 indent = ' '*isp.indent_spaces
635 if autoindent:
635 if autoindent:
636 line = indent + raw_input(prompt+indent)
636 line = indent + raw_input(prompt+indent)
637 else:
637 else:
638 line = raw_input(prompt)
638 line = raw_input(prompt)
639 isp.push(line)
639 isp.push(line)
640 prompt = '... '
640 prompt = '... '
641
641
642 # Here we just return input so we can use it in a test suite, but a
642 # Here we just return input so we can use it in a test suite, but a
643 # real interpreter would instead send it for execution somewhere.
643 # real interpreter would instead send it for execution somewhere.
644 #src = isp.source; raise EOFError # dbg
644 #src = isp.source; raise EOFError # dbg
645 src = isp.source_reset()
645 src = isp.source_reset()
646 print 'Input source was:\n', src
646 print 'Input source was:\n', src
647 except EOFError:
647 except EOFError:
648 print 'Bye'
648 print 'Bye'
@@ -1,434 +1,434 b''
1 # Standard library imports
1 # Standard library imports
2 import signal
2 import signal
3 import sys
3 import sys
4
4
5 # System library imports
5 # System library imports
6 from pygments.lexers import PythonLexer
6 from pygments.lexers import PythonLexer
7 from PyQt4 import QtCore, QtGui
7 from PyQt4 import QtCore, QtGui
8 import zmq
8 import zmq
9
9
10 # Local imports
10 # Local imports
11 from IPython.core.inputsplitter import InputSplitter
11 from IPython.core.inputsplitter import InputSplitter
12 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
12 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
13 from call_tip_widget import CallTipWidget
13 from call_tip_widget import CallTipWidget
14 from completion_lexer import CompletionLexer
14 from completion_lexer import CompletionLexer
15 from console_widget import HistoryConsoleWidget
15 from console_widget import HistoryConsoleWidget
16 from pygments_highlighter import PygmentsHighlighter
16 from pygments_highlighter import PygmentsHighlighter
17
17
18
18
19 class FrontendHighlighter(PygmentsHighlighter):
19 class FrontendHighlighter(PygmentsHighlighter):
20 """ A PygmentsHighlighter that can be turned on and off and that ignores
20 """ A PygmentsHighlighter that can be turned on and off and that ignores
21 prompts.
21 prompts.
22 """
22 """
23
23
24 def __init__(self, frontend):
24 def __init__(self, frontend):
25 super(FrontendHighlighter, self).__init__(frontend._control.document())
25 super(FrontendHighlighter, self).__init__(frontend._control.document())
26 self._current_offset = 0
26 self._current_offset = 0
27 self._frontend = frontend
27 self._frontend = frontend
28 self.highlighting_on = False
28 self.highlighting_on = False
29
29
30 def highlightBlock(self, qstring):
30 def highlightBlock(self, qstring):
31 """ Highlight a block of text. Reimplemented to highlight selectively.
31 """ Highlight a block of text. Reimplemented to highlight selectively.
32 """
32 """
33 if not self.highlighting_on:
33 if not self.highlighting_on:
34 return
34 return
35
35
36 # The input to this function is unicode string that may contain
36 # The input to this function is unicode string that may contain
37 # paragraph break characters, non-breaking spaces, etc. Here we acquire
37 # paragraph break characters, non-breaking spaces, etc. Here we acquire
38 # the string as plain text so we can compare it.
38 # the string as plain text so we can compare it.
39 current_block = self.currentBlock()
39 current_block = self.currentBlock()
40 string = self._frontend._get_block_plain_text(current_block)
40 string = self._frontend._get_block_plain_text(current_block)
41
41
42 # Decide whether to check for the regular or continuation prompt.
42 # Decide whether to check for the regular or continuation prompt.
43 if current_block.contains(self._frontend._prompt_pos):
43 if current_block.contains(self._frontend._prompt_pos):
44 prompt = self._frontend._prompt
44 prompt = self._frontend._prompt
45 else:
45 else:
46 prompt = self._frontend._continuation_prompt
46 prompt = self._frontend._continuation_prompt
47
47
48 # Don't highlight the part of the string that contains the prompt.
48 # Don't highlight the part of the string that contains the prompt.
49 if string.startswith(prompt):
49 if string.startswith(prompt):
50 self._current_offset = len(prompt)
50 self._current_offset = len(prompt)
51 qstring.remove(0, len(prompt))
51 qstring.remove(0, len(prompt))
52 else:
52 else:
53 self._current_offset = 0
53 self._current_offset = 0
54
54
55 PygmentsHighlighter.highlightBlock(self, qstring)
55 PygmentsHighlighter.highlightBlock(self, qstring)
56
56
57 def rehighlightBlock(self, block):
57 def rehighlightBlock(self, block):
58 """ Reimplemented to temporarily enable highlighting if disabled.
58 """ Reimplemented to temporarily enable highlighting if disabled.
59 """
59 """
60 old = self.highlighting_on
60 old = self.highlighting_on
61 self.highlighting_on = True
61 self.highlighting_on = True
62 super(FrontendHighlighter, self).rehighlightBlock(block)
62 super(FrontendHighlighter, self).rehighlightBlock(block)
63 self.highlighting_on = old
63 self.highlighting_on = old
64
64
65 def setFormat(self, start, count, format):
65 def setFormat(self, start, count, format):
66 """ Reimplemented to highlight selectively.
66 """ Reimplemented to highlight selectively.
67 """
67 """
68 start += self._current_offset
68 start += self._current_offset
69 PygmentsHighlighter.setFormat(self, start, count, format)
69 PygmentsHighlighter.setFormat(self, start, count, format)
70
70
71
71
72 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
72 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
73 """ A Qt frontend for a generic Python kernel.
73 """ A Qt frontend for a generic Python kernel.
74 """
74 """
75
75
76 # An option and corresponding signal for overriding the default kernel
76 # An option and corresponding signal for overriding the default kernel
77 # interrupt behavior.
77 # interrupt behavior.
78 custom_interrupt = False
78 custom_interrupt = False
79 custom_interrupt_requested = QtCore.pyqtSignal()
79 custom_interrupt_requested = QtCore.pyqtSignal()
80
80
81 # An option and corresponding signal for overriding the default kernel
81 # An option and corresponding signal for overriding the default kernel
82 # restart behavior.
82 # restart behavior.
83 custom_restart = False
83 custom_restart = False
84 custom_restart_requested = QtCore.pyqtSignal()
84 custom_restart_requested = QtCore.pyqtSignal()
85
85
86 # Emitted when an 'execute_reply' has been received from the kernel and
86 # Emitted when an 'execute_reply' has been received from the kernel and
87 # processed by the FrontendWidget.
87 # processed by the FrontendWidget.
88 executed = QtCore.pyqtSignal(object)
88 executed = QtCore.pyqtSignal(object)
89
89
90 # Protected class variables.
90 # Protected class variables.
91 _highlighter_class = FrontendHighlighter
91 _highlighter_class = FrontendHighlighter
92 _input_splitter_class = InputSplitter
92 _input_splitter_class = InputSplitter
93
93
94 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
95 # 'object' interface
95 # 'object' interface
96 #---------------------------------------------------------------------------
96 #---------------------------------------------------------------------------
97
97
98 def __init__(self, *args, **kw):
98 def __init__(self, *args, **kw):
99 super(FrontendWidget, self).__init__(*args, **kw)
99 super(FrontendWidget, self).__init__(*args, **kw)
100
100
101 # FrontendWidget protected variables.
101 # FrontendWidget protected variables.
102 self._call_tip_widget = CallTipWidget(self._control)
102 self._call_tip_widget = CallTipWidget(self._control)
103 self._completion_lexer = CompletionLexer(PythonLexer())
103 self._completion_lexer = CompletionLexer(PythonLexer())
104 self._hidden = False
104 self._hidden = False
105 self._highlighter = self._highlighter_class(self)
105 self._highlighter = self._highlighter_class(self)
106 self._input_splitter = self._input_splitter_class(input_mode='replace')
106 self._input_splitter = self._input_splitter_class(input_mode='block')
107 self._kernel_manager = None
107 self._kernel_manager = None
108
108
109 # Configure the ConsoleWidget.
109 # Configure the ConsoleWidget.
110 self.tab_width = 4
110 self.tab_width = 4
111 self._set_continuation_prompt('... ')
111 self._set_continuation_prompt('... ')
112
112
113 # Connect signal handlers.
113 # Connect signal handlers.
114 document = self._control.document()
114 document = self._control.document()
115 document.contentsChange.connect(self._document_contents_change)
115 document.contentsChange.connect(self._document_contents_change)
116
116
117 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
118 # 'ConsoleWidget' abstract interface
118 # 'ConsoleWidget' abstract interface
119 #---------------------------------------------------------------------------
119 #---------------------------------------------------------------------------
120
120
121 def _is_complete(self, source, interactive):
121 def _is_complete(self, source, interactive):
122 """ Returns whether 'source' can be completely processed and a new
122 """ Returns whether 'source' can be completely processed and a new
123 prompt created. When triggered by an Enter/Return key press,
123 prompt created. When triggered by an Enter/Return key press,
124 'interactive' is True; otherwise, it is False.
124 'interactive' is True; otherwise, it is False.
125 """
125 """
126 complete = self._input_splitter.push(source.expandtabs(4))
126 complete = self._input_splitter.push(source.expandtabs(4))
127 if interactive:
127 if interactive:
128 complete = not self._input_splitter.push_accepts_more()
128 complete = not self._input_splitter.push_accepts_more()
129 return complete
129 return complete
130
130
131 def _execute(self, source, hidden):
131 def _execute(self, source, hidden):
132 """ Execute 'source'. If 'hidden', do not show any output.
132 """ Execute 'source'. If 'hidden', do not show any output.
133 """
133 """
134 self.kernel_manager.xreq_channel.execute(source, hidden)
134 self.kernel_manager.xreq_channel.execute(source, hidden)
135 self._hidden = hidden
135 self._hidden = hidden
136
136
137 def _prompt_started_hook(self):
137 def _prompt_started_hook(self):
138 """ Called immediately after a new prompt is displayed.
138 """ Called immediately after a new prompt is displayed.
139 """
139 """
140 if not self._reading:
140 if not self._reading:
141 self._highlighter.highlighting_on = True
141 self._highlighter.highlighting_on = True
142
142
143 def _prompt_finished_hook(self):
143 def _prompt_finished_hook(self):
144 """ Called immediately after a prompt is finished, i.e. when some input
144 """ Called immediately after a prompt is finished, i.e. when some input
145 will be processed and a new prompt displayed.
145 will be processed and a new prompt displayed.
146 """
146 """
147 if not self._reading:
147 if not self._reading:
148 self._highlighter.highlighting_on = False
148 self._highlighter.highlighting_on = False
149
149
150 def _tab_pressed(self):
150 def _tab_pressed(self):
151 """ Called when the tab key is pressed. Returns whether to continue
151 """ Called when the tab key is pressed. Returns whether to continue
152 processing the event.
152 processing the event.
153 """
153 """
154 # Perform tab completion if:
154 # Perform tab completion if:
155 # 1) The cursor is in the input buffer.
155 # 1) The cursor is in the input buffer.
156 # 2) There is a non-whitespace character before the cursor.
156 # 2) There is a non-whitespace character before the cursor.
157 text = self._get_input_buffer_cursor_line()
157 text = self._get_input_buffer_cursor_line()
158 if text is None:
158 if text is None:
159 return False
159 return False
160 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
160 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
161 if complete:
161 if complete:
162 self._complete()
162 self._complete()
163 return not complete
163 return not complete
164
164
165 #---------------------------------------------------------------------------
165 #---------------------------------------------------------------------------
166 # 'ConsoleWidget' protected interface
166 # 'ConsoleWidget' protected interface
167 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
168
168
169 def _event_filter_console_keypress(self, event):
169 def _event_filter_console_keypress(self, event):
170 """ Reimplemented to allow execution interruption.
170 """ Reimplemented to allow execution interruption.
171 """
171 """
172 key = event.key()
172 key = event.key()
173 if self._executing and self._control_key_down(event.modifiers()):
173 if self._executing and self._control_key_down(event.modifiers()):
174 if key == QtCore.Qt.Key_C:
174 if key == QtCore.Qt.Key_C:
175 self._kernel_interrupt()
175 self._kernel_interrupt()
176 return True
176 return True
177 elif key == QtCore.Qt.Key_Period:
177 elif key == QtCore.Qt.Key_Period:
178 self._kernel_restart()
178 self._kernel_restart()
179 return True
179 return True
180 return super(FrontendWidget, self)._event_filter_console_keypress(event)
180 return super(FrontendWidget, self)._event_filter_console_keypress(event)
181
181
182 def _show_continuation_prompt(self):
182 def _show_continuation_prompt(self):
183 """ Reimplemented for auto-indentation.
183 """ Reimplemented for auto-indentation.
184 """
184 """
185 super(FrontendWidget, self)._show_continuation_prompt()
185 super(FrontendWidget, self)._show_continuation_prompt()
186 spaces = self._input_splitter.indent_spaces
186 spaces = self._input_splitter.indent_spaces
187 self._append_plain_text('\t' * (spaces / self.tab_width))
187 self._append_plain_text('\t' * (spaces / self.tab_width))
188 self._append_plain_text(' ' * (spaces % self.tab_width))
188 self._append_plain_text(' ' * (spaces % self.tab_width))
189
189
190 #---------------------------------------------------------------------------
190 #---------------------------------------------------------------------------
191 # 'BaseFrontendMixin' abstract interface
191 # 'BaseFrontendMixin' abstract interface
192 #---------------------------------------------------------------------------
192 #---------------------------------------------------------------------------
193
193
194 def _handle_complete_reply(self, rep):
194 def _handle_complete_reply(self, rep):
195 """ Handle replies for tab completion.
195 """ Handle replies for tab completion.
196 """
196 """
197 cursor = self._get_cursor()
197 cursor = self._get_cursor()
198 if rep['parent_header']['msg_id'] == self._complete_id and \
198 if rep['parent_header']['msg_id'] == self._complete_id and \
199 cursor.position() == self._complete_pos:
199 cursor.position() == self._complete_pos:
200 # The completer tells us what text was actually used for the
200 # The completer tells us what text was actually used for the
201 # matching, so we must move that many characters left to apply the
201 # matching, so we must move that many characters left to apply the
202 # completions.
202 # completions.
203 text = rep['content']['matched_text']
203 text = rep['content']['matched_text']
204 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
204 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
205 self._complete_with_items(cursor, rep['content']['matches'])
205 self._complete_with_items(cursor, rep['content']['matches'])
206
206
207 def _handle_execute_reply(self, msg):
207 def _handle_execute_reply(self, msg):
208 """ Handles replies for code execution.
208 """ Handles replies for code execution.
209 """
209 """
210 if not self._hidden:
210 if not self._hidden:
211 # Make sure that all output from the SUB channel has been processed
211 # Make sure that all output from the SUB channel has been processed
212 # before writing a new prompt.
212 # before writing a new prompt.
213 self.kernel_manager.sub_channel.flush()
213 self.kernel_manager.sub_channel.flush()
214
214
215 content = msg['content']
215 content = msg['content']
216 status = content['status']
216 status = content['status']
217 if status == 'ok':
217 if status == 'ok':
218 self._process_execute_ok(msg)
218 self._process_execute_ok(msg)
219 elif status == 'error':
219 elif status == 'error':
220 self._process_execute_error(msg)
220 self._process_execute_error(msg)
221 elif status == 'abort':
221 elif status == 'abort':
222 self._process_execute_abort(msg)
222 self._process_execute_abort(msg)
223
223
224 self._show_interpreter_prompt_for_reply(msg)
224 self._show_interpreter_prompt_for_reply(msg)
225 self.executed.emit(msg)
225 self.executed.emit(msg)
226
226
227 def _handle_input_request(self, msg):
227 def _handle_input_request(self, msg):
228 """ Handle requests for raw_input.
228 """ Handle requests for raw_input.
229 """
229 """
230 if self._hidden:
230 if self._hidden:
231 raise RuntimeError('Request for raw input during hidden execution.')
231 raise RuntimeError('Request for raw input during hidden execution.')
232
232
233 # Make sure that all output from the SUB channel has been processed
233 # Make sure that all output from the SUB channel has been processed
234 # before entering readline mode.
234 # before entering readline mode.
235 self.kernel_manager.sub_channel.flush()
235 self.kernel_manager.sub_channel.flush()
236
236
237 def callback(line):
237 def callback(line):
238 self.kernel_manager.rep_channel.input(line)
238 self.kernel_manager.rep_channel.input(line)
239 self._readline(msg['content']['prompt'], callback=callback)
239 self._readline(msg['content']['prompt'], callback=callback)
240
240
241 def _handle_object_info_reply(self, rep):
241 def _handle_object_info_reply(self, rep):
242 """ Handle replies for call tips.
242 """ Handle replies for call tips.
243 """
243 """
244 cursor = self._get_cursor()
244 cursor = self._get_cursor()
245 if rep['parent_header']['msg_id'] == self._call_tip_id and \
245 if rep['parent_header']['msg_id'] == self._call_tip_id and \
246 cursor.position() == self._call_tip_pos:
246 cursor.position() == self._call_tip_pos:
247 doc = rep['content']['docstring']
247 doc = rep['content']['docstring']
248 if doc:
248 if doc:
249 self._call_tip_widget.show_docstring(doc)
249 self._call_tip_widget.show_docstring(doc)
250
250
251 def _handle_pyout(self, msg):
251 def _handle_pyout(self, msg):
252 """ Handle display hook output.
252 """ Handle display hook output.
253 """
253 """
254 if not self._hidden and self._is_from_this_session(msg):
254 if not self._hidden and self._is_from_this_session(msg):
255 self._append_plain_text(msg['content']['data'] + '\n')
255 self._append_plain_text(msg['content']['data'] + '\n')
256
256
257 def _handle_stream(self, msg):
257 def _handle_stream(self, msg):
258 """ Handle stdout, stderr, and stdin.
258 """ Handle stdout, stderr, and stdin.
259 """
259 """
260 if not self._hidden and self._is_from_this_session(msg):
260 if not self._hidden and self._is_from_this_session(msg):
261 self._append_plain_text(msg['content']['data'])
261 self._append_plain_text(msg['content']['data'])
262 self._control.moveCursor(QtGui.QTextCursor.End)
262 self._control.moveCursor(QtGui.QTextCursor.End)
263
263
264 def _started_channels(self):
264 def _started_channels(self):
265 """ Called when the KernelManager channels have started listening or
265 """ Called when the KernelManager channels have started listening or
266 when the frontend is assigned an already listening KernelManager.
266 when the frontend is assigned an already listening KernelManager.
267 """
267 """
268 self._control.clear()
268 self._control.clear()
269 self._append_plain_text(self._get_banner())
269 self._append_plain_text(self._get_banner())
270 self._show_interpreter_prompt()
270 self._show_interpreter_prompt()
271
271
272 def _stopped_channels(self):
272 def _stopped_channels(self):
273 """ Called when the KernelManager channels have stopped listening or
273 """ Called when the KernelManager channels have stopped listening or
274 when a listening KernelManager is removed from the frontend.
274 when a listening KernelManager is removed from the frontend.
275 """
275 """
276 self._executing = self._reading = False
276 self._executing = self._reading = False
277 self._highlighter.highlighting_on = False
277 self._highlighter.highlighting_on = False
278
278
279 #---------------------------------------------------------------------------
279 #---------------------------------------------------------------------------
280 # 'FrontendWidget' interface
280 # 'FrontendWidget' interface
281 #---------------------------------------------------------------------------
281 #---------------------------------------------------------------------------
282
282
283 def execute_file(self, path, hidden=False):
283 def execute_file(self, path, hidden=False):
284 """ Attempts to execute file with 'path'. If 'hidden', no output is
284 """ Attempts to execute file with 'path'. If 'hidden', no output is
285 shown.
285 shown.
286 """
286 """
287 self.execute('execfile("%s")' % path, hidden=hidden)
287 self.execute('execfile("%s")' % path, hidden=hidden)
288
288
289 #---------------------------------------------------------------------------
289 #---------------------------------------------------------------------------
290 # 'FrontendWidget' protected interface
290 # 'FrontendWidget' protected interface
291 #---------------------------------------------------------------------------
291 #---------------------------------------------------------------------------
292
292
293 def _call_tip(self):
293 def _call_tip(self):
294 """ Shows a call tip, if appropriate, at the current cursor location.
294 """ Shows a call tip, if appropriate, at the current cursor location.
295 """
295 """
296 # Decide if it makes sense to show a call tip
296 # Decide if it makes sense to show a call tip
297 cursor = self._get_cursor()
297 cursor = self._get_cursor()
298 cursor.movePosition(QtGui.QTextCursor.Left)
298 cursor.movePosition(QtGui.QTextCursor.Left)
299 document = self._control.document()
299 document = self._control.document()
300 if document.characterAt(cursor.position()).toAscii() != '(':
300 if document.characterAt(cursor.position()).toAscii() != '(':
301 return False
301 return False
302 context = self._get_context(cursor)
302 context = self._get_context(cursor)
303 if not context:
303 if not context:
304 return False
304 return False
305
305
306 # Send the metadata request to the kernel
306 # Send the metadata request to the kernel
307 name = '.'.join(context)
307 name = '.'.join(context)
308 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
308 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
309 self._call_tip_pos = self._get_cursor().position()
309 self._call_tip_pos = self._get_cursor().position()
310 return True
310 return True
311
311
312 def _complete(self):
312 def _complete(self):
313 """ Performs completion at the current cursor location.
313 """ Performs completion at the current cursor location.
314 """
314 """
315 # Decide if it makes sense to do completion
315 # Decide if it makes sense to do completion
316
316
317 # We should return only if the line is empty. Otherwise, let the
317 # We should return only if the line is empty. Otherwise, let the
318 # kernel split the line up.
318 # kernel split the line up.
319 line = self._get_input_buffer_cursor_line()
319 line = self._get_input_buffer_cursor_line()
320 if not line:
320 if not line:
321 return False
321 return False
322
322
323 # We let the kernel split the input line, so we *always* send an empty
323 # We let the kernel split the input line, so we *always* send an empty
324 # text field. Readline-based frontends do get a real text field which
324 # text field. Readline-based frontends do get a real text field which
325 # they can use.
325 # they can use.
326 text = ''
326 text = ''
327
327
328 # Send the completion request to the kernel
328 # Send the completion request to the kernel
329 self._complete_id = self.kernel_manager.xreq_channel.complete(
329 self._complete_id = self.kernel_manager.xreq_channel.complete(
330 text, # text
330 text, # text
331 line, # line
331 line, # line
332 self._get_input_buffer_cursor_column(), # cursor_pos
332 self._get_input_buffer_cursor_column(), # cursor_pos
333 self.input_buffer) # block
333 self.input_buffer) # block
334 self._complete_pos = self._get_cursor().position()
334 self._complete_pos = self._get_cursor().position()
335 return True
335 return True
336
336
337 def _get_banner(self):
337 def _get_banner(self):
338 """ Gets a banner to display at the beginning of a session.
338 """ Gets a banner to display at the beginning of a session.
339 """
339 """
340 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
340 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
341 '"license" for more information.'
341 '"license" for more information.'
342 return banner % (sys.version, sys.platform)
342 return banner % (sys.version, sys.platform)
343
343
344 def _get_context(self, cursor=None):
344 def _get_context(self, cursor=None):
345 """ Gets the context at the current cursor location.
345 """ Gets the context at the current cursor location.
346 """
346 """
347 if cursor is None:
347 if cursor is None:
348 cursor = self._get_cursor()
348 cursor = self._get_cursor()
349 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
349 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
350 QtGui.QTextCursor.KeepAnchor)
350 QtGui.QTextCursor.KeepAnchor)
351 text = str(cursor.selection().toPlainText())
351 text = str(cursor.selection().toPlainText())
352 return self._completion_lexer.get_context(text)
352 return self._completion_lexer.get_context(text)
353
353
354 def _kernel_interrupt(self):
354 def _kernel_interrupt(self):
355 """ Attempts to interrupt the running kernel.
355 """ Attempts to interrupt the running kernel.
356 """
356 """
357 if self.custom_interrupt:
357 if self.custom_interrupt:
358 self.custom_interrupt_requested.emit()
358 self.custom_interrupt_requested.emit()
359 elif self.kernel_manager.has_kernel:
359 elif self.kernel_manager.has_kernel:
360 self.kernel_manager.signal_kernel(signal.SIGINT)
360 self.kernel_manager.signal_kernel(signal.SIGINT)
361 else:
361 else:
362 self._append_plain_text('Kernel process is either remote or '
362 self._append_plain_text('Kernel process is either remote or '
363 'unspecified. Cannot interrupt.\n')
363 'unspecified. Cannot interrupt.\n')
364
364
365 def _kernel_restart(self):
365 def _kernel_restart(self):
366 """ Attempts to restart the running kernel.
366 """ Attempts to restart the running kernel.
367 """
367 """
368 if self.custom_restart:
368 if self.custom_restart:
369 self.custom_restart_requested.emit()
369 self.custom_restart_requested.emit()
370 elif self.kernel_manager.has_kernel:
370 elif self.kernel_manager.has_kernel:
371 try:
371 try:
372 self.kernel_manager.restart_kernel()
372 self.kernel_manager.restart_kernel()
373 except RuntimeError:
373 except RuntimeError:
374 message = 'Kernel started externally. Cannot restart.\n'
374 message = 'Kernel started externally. Cannot restart.\n'
375 self._append_plain_text(message)
375 self._append_plain_text(message)
376 else:
376 else:
377 self._stopped_channels()
377 self._stopped_channels()
378 self._append_plain_text('Kernel restarting...\n')
378 self._append_plain_text('Kernel restarting...\n')
379 self._show_interpreter_prompt()
379 self._show_interpreter_prompt()
380 else:
380 else:
381 self._append_plain_text('Kernel process is either remote or '
381 self._append_plain_text('Kernel process is either remote or '
382 'unspecified. Cannot restart.\n')
382 'unspecified. Cannot restart.\n')
383
383
384 def _process_execute_abort(self, msg):
384 def _process_execute_abort(self, msg):
385 """ Process a reply for an aborted execution request.
385 """ Process a reply for an aborted execution request.
386 """
386 """
387 self._append_plain_text("ERROR: execution aborted\n")
387 self._append_plain_text("ERROR: execution aborted\n")
388
388
389 def _process_execute_error(self, msg):
389 def _process_execute_error(self, msg):
390 """ Process a reply for an execution request that resulted in an error.
390 """ Process a reply for an execution request that resulted in an error.
391 """
391 """
392 content = msg['content']
392 content = msg['content']
393 traceback = ''.join(content['traceback'])
393 traceback = ''.join(content['traceback'])
394 self._append_plain_text(traceback)
394 self._append_plain_text(traceback)
395
395
396 def _process_execute_ok(self, msg):
396 def _process_execute_ok(self, msg):
397 """ Process a reply for a successful execution equest.
397 """ Process a reply for a successful execution equest.
398 """
398 """
399 payload = msg['content']['payload']
399 payload = msg['content']['payload']
400 for item in payload:
400 for item in payload:
401 if not self._process_execute_payload(item):
401 if not self._process_execute_payload(item):
402 warning = 'Received unknown payload of type %s\n'
402 warning = 'Received unknown payload of type %s\n'
403 self._append_plain_text(warning % repr(item['source']))
403 self._append_plain_text(warning % repr(item['source']))
404
404
405 def _process_execute_payload(self, item):
405 def _process_execute_payload(self, item):
406 """ Process a single payload item from the list of payload items in an
406 """ Process a single payload item from the list of payload items in an
407 execution reply. Returns whether the payload was handled.
407 execution reply. Returns whether the payload was handled.
408 """
408 """
409 # The basic FrontendWidget doesn't handle payloads, as they are a
409 # The basic FrontendWidget doesn't handle payloads, as they are a
410 # mechanism for going beyond the standard Python interpreter model.
410 # mechanism for going beyond the standard Python interpreter model.
411 return False
411 return False
412
412
413 def _show_interpreter_prompt(self):
413 def _show_interpreter_prompt(self):
414 """ Shows a prompt for the interpreter.
414 """ Shows a prompt for the interpreter.
415 """
415 """
416 self._show_prompt('>>> ')
416 self._show_prompt('>>> ')
417
417
418 def _show_interpreter_prompt_for_reply(self, msg):
418 def _show_interpreter_prompt_for_reply(self, msg):
419 """ Shows a prompt for the interpreter given an 'execute_reply' message.
419 """ Shows a prompt for the interpreter given an 'execute_reply' message.
420 """
420 """
421 self._show_interpreter_prompt()
421 self._show_interpreter_prompt()
422
422
423 #------ Signal handlers ----------------------------------------------------
423 #------ Signal handlers ----------------------------------------------------
424
424
425 def _document_contents_change(self, position, removed, added):
425 def _document_contents_change(self, position, removed, added):
426 """ Called whenever the document's content changes. Display a call tip
426 """ Called whenever the document's content changes. Display a call tip
427 if appropriate.
427 if appropriate.
428 """
428 """
429 # Calculate where the cursor should be *after* the change:
429 # Calculate where the cursor should be *after* the change:
430 position += added
430 position += added
431
431
432 document = self._control.document()
432 document = self._control.document()
433 if position == self._get_cursor().position():
433 if position == self._get_cursor().position():
434 self._call_tip()
434 self._call_tip()
General Comments 0
You need to be logged in to leave comments. Login now