##// END OF EJS Templates
Fix bug with IPythonInputSplitter in block input mode.
Fernando Perez -
Show More
@@ -1,839 +1,858 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
75 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
76 # Globals
75 # Globals
77 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
78
77
79 # The escape sequences that define the syntax transformations IPython will
78 # The escape sequences that define the syntax transformations IPython will
80 # 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
81 # 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
82 # 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
83 # should be considered fixed.
82 # should be considered fixed.
84
83
85 ESC_SHELL = '!'
84 ESC_SHELL = '!'
86 ESC_SH_CAP = '!!'
85 ESC_SH_CAP = '!!'
87 ESC_HELP = '?'
86 ESC_HELP = '?'
88 ESC_HELP2 = '??'
87 ESC_HELP2 = '??'
89 ESC_MAGIC = '%'
88 ESC_MAGIC = '%'
90 ESC_QUOTE = ','
89 ESC_QUOTE = ','
91 ESC_QUOTE2 = ';'
90 ESC_QUOTE2 = ';'
92 ESC_PAREN = '/'
91 ESC_PAREN = '/'
93
92
94 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
95 # Utilities
94 # Utilities
96 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
97
96
98 # 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
99 # 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
100 # 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
101 # while developing.
100 # while developing.
102
101
103 # compiled regexps for autoindent management
102 # compiled regexps for autoindent management
104 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
103 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
105 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
104 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
106
105
107
106
108 def num_ini_spaces(s):
107 def num_ini_spaces(s):
109 """Return the number of initial spaces in a string.
108 """Return the number of initial spaces in a string.
110
109
111 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
112 mixing of tabs and spaces in the user's input.
111 mixing of tabs and spaces in the user's input.
113
112
114 Parameters
113 Parameters
115 ----------
114 ----------
116 s : string
115 s : string
117
116
118 Returns
117 Returns
119 -------
118 -------
120 n : int
119 n : int
121 """
120 """
122
121
123 ini_spaces = ini_spaces_re.match(s)
122 ini_spaces = ini_spaces_re.match(s)
124 if ini_spaces:
123 if ini_spaces:
125 return ini_spaces.end()
124 return ini_spaces.end()
126 else:
125 else:
127 return 0
126 return 0
128
127
129
128
130 def remove_comments(src):
129 def remove_comments(src):
131 """Remove all comments from input source.
130 """Remove all comments from input source.
132
131
133 Note: comments are NOT recognized inside of strings!
132 Note: comments are NOT recognized inside of strings!
134
133
135 Parameters
134 Parameters
136 ----------
135 ----------
137 src : string
136 src : string
138 A single or multiline input string.
137 A single or multiline input string.
139
138
140 Returns
139 Returns
141 -------
140 -------
142 String with all Python comments removed.
141 String with all Python comments removed.
143 """
142 """
144
143
145 return re.sub('#.*', '', src)
144 return re.sub('#.*', '', src)
146
145
147
146
148 def get_input_encoding():
147 def get_input_encoding():
149 """Return the default standard input encoding.
148 """Return the default standard input encoding.
150
149
151 If sys.stdin has no encoding, 'ascii' is returned."""
150 If sys.stdin has no encoding, 'ascii' is returned."""
152 # 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
153 # ensure that a valid encoding is returned.
152 # ensure that a valid encoding is returned.
154 encoding = getattr(sys.stdin, 'encoding', None)
153 encoding = getattr(sys.stdin, 'encoding', None)
155 if encoding is None:
154 if encoding is None:
156 encoding = 'ascii'
155 encoding = 'ascii'
157 return encoding
156 return encoding
158
157
159 #-----------------------------------------------------------------------------
158 #-----------------------------------------------------------------------------
160 # Classes and functions for normal Python syntax handling
159 # Classes and functions for normal Python syntax handling
161 #-----------------------------------------------------------------------------
160 #-----------------------------------------------------------------------------
162
161
163 class InputSplitter(object):
162 class InputSplitter(object):
164 """An object that can split Python source input in executable blocks.
163 """An object that can split Python source input in executable blocks.
165
164
166 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:
167
166
168 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
169 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
170 could be executed already. In addition, it provides a method called
169 could be executed already. In addition, it provides a method called
171 :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
172 can be pushed into a single interactive block.
171 can be pushed into a single interactive block.
173
172
174 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,
175 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
176 interactively as a single statement.
175 interactively as a single statement.
177
176
178 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
179 this tool::
178 this tool::
180
179
181 isp = InputSplitter()
180 isp = InputSplitter()
182 while isp.push_accepts_more():
181 while isp.push_accepts_more():
183 indent = ' '*isp.indent_spaces
182 indent = ' '*isp.indent_spaces
184 prompt = '>>> ' + indent
183 prompt = '>>> ' + indent
185 line = indent + raw_input(prompt)
184 line = indent + raw_input(prompt)
186 isp.push(line)
185 isp.push(line)
187 print 'Input source was:\n', isp.source_reset(),
186 print 'Input source was:\n', isp.source_reset(),
188 """
187 """
189 # 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
190 # 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
191 # indentation level, in order to provide auto-indent facilities.
190 # indentation level, in order to provide auto-indent facilities.
192 indent_spaces = 0
191 indent_spaces = 0
193 # String, indicating the default input encoding. It is computed by default
192 # String, indicating the default input encoding. It is computed by default
194 # 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
195 # client with specific knowledge of the encoding.
194 # client with specific knowledge of the encoding.
196 encoding = ''
195 encoding = ''
197 # String where the current full source input is stored, properly encoded.
196 # String where the current full source input is stored, properly encoded.
198 # 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
199 # source code, that has been properly encoded.
198 # source code, that has been properly encoded.
200 source = ''
199 source = ''
201 # Code object corresponding to the current source. It is automatically
200 # Code object corresponding to the current source. It is automatically
202 # 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
203 # 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.
204 code = None
203 code = None
205 # Input mode
204 # Input mode
206 input_mode = 'append'
205 input_mode = 'append'
207
206
208 # Private attributes
207 # Private attributes
209
208
210 # List with lines of input accumulated so far
209 # List with lines of input accumulated so far
211 _buffer = None
210 _buffer = None
212 # Command compiler
211 # Command compiler
213 _compile = None
212 _compile = None
214 # 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
215 _full_dedent = False
214 _full_dedent = False
216 # Boolean indicating whether the current block is complete
215 # Boolean indicating whether the current block is complete
217 _is_complete = None
216 _is_complete = None
218
217
219 def __init__(self, input_mode=None):
218 def __init__(self, input_mode=None):
220 """Create a new InputSplitter instance.
219 """Create a new InputSplitter instance.
221
220
222 Parameters
221 Parameters
223 ----------
222 ----------
224 input_mode : str
223 input_mode : str
225
224
226 One of 'append', 'replace', default is 'append'. This controls how
225 One of ['append', 'replace']; default is 'append'. This controls how
227 new inputs are used: in 'append' mode, they are appended to the
226 new inputs are used: in 'append' mode, they are appended to the
228 existing buffer and the whole buffer is compiled; in 'replace' mode,
227 existing buffer and the whole buffer is compiled; in 'replace' mode,
229 each new input completely replaces all prior inputs. Replace mode is
228 each new input completely replaces all prior inputs. Replace mode is
230 thus equivalent to prepending a full reset() to every push() call.
229 thus equivalent to prepending a full reset() to every push() call.
231
230
232 In practice, line-oriented clients likely want to use 'append' mode
231 In practice, line-oriented clients likely want to use 'append' mode
233 while block-oriented ones will want to use 'replace'.
232 while block-oriented ones will want to use 'replace'.
234 """
233 """
235 self._buffer = []
234 self._buffer = []
236 self._compile = codeop.CommandCompiler()
235 self._compile = codeop.CommandCompiler()
237 self.encoding = get_input_encoding()
236 self.encoding = get_input_encoding()
238 self.input_mode = InputSplitter.input_mode if input_mode is None \
237 self.input_mode = InputSplitter.input_mode if input_mode is None \
239 else input_mode
238 else input_mode
240
239
241 def reset(self):
240 def reset(self):
242 """Reset the input buffer and associated state."""
241 """Reset the input buffer and associated state."""
243 self.indent_spaces = 0
242 self.indent_spaces = 0
244 self._buffer[:] = []
243 self._buffer[:] = []
245 self.source = ''
244 self.source = ''
246 self.code = None
245 self.code = None
247 self._is_complete = False
246 self._is_complete = False
248 self._full_dedent = False
247 self._full_dedent = False
249
248
250 def source_reset(self):
249 def source_reset(self):
251 """Return the input source and perform a full reset.
250 """Return the input source and perform a full reset.
252 """
251 """
253 out = self.source
252 out = self.source
254 self.reset()
253 self.reset()
255 return out
254 return out
256
255
257 def push(self, lines):
256 def push(self, lines):
258 """Push one ore more lines of input.
257 """Push one ore more lines of input.
259
258
260 This stores the given lines and returns a status code indicating
259 This stores the given lines and returns a status code indicating
261 whether the code forms a complete Python block or not.
260 whether the code forms a complete Python block or not.
262
261
263 Any exceptions generated in compilation are swallowed, but if an
262 Any exceptions generated in compilation are swallowed, but if an
264 exception was produced, the method returns True.
263 exception was produced, the method returns True.
265
264
266 Parameters
265 Parameters
267 ----------
266 ----------
268 lines : string
267 lines : string
269 One or more lines of Python input.
268 One or more lines of Python input.
270
269
271 Returns
270 Returns
272 -------
271 -------
273 is_complete : boolean
272 is_complete : boolean
274 True if the current input source (the result of the current input
273 True if the current input source (the result of the current input
275 plus prior inputs) forms a complete Python execution block. Note that
274 plus prior inputs) forms a complete Python execution block. Note that
276 this value is also stored as a private attribute (_is_complete), so it
275 this value is also stored as a private attribute (_is_complete), so it
277 can be queried at any time.
276 can be queried at any time.
278 """
277 """
279 if self.input_mode == 'replace':
278 if self.input_mode == 'replace':
280 self.reset()
279 self.reset()
281
280
282 # If the source code has leading blanks, add 'if 1:\n' to it
281 # If the source code has leading blanks, add 'if 1:\n' to it
283 # this allows execution of indented pasted code. It is tempting
282 # this allows execution of indented pasted code. It is tempting
284 # to add '\n' at the end of source to run commands like ' a=1'
283 # to add '\n' at the end of source to run commands like ' a=1'
285 # directly, but this fails for more complicated scenarios
284 # directly, but this fails for more complicated scenarios
286 if not self._buffer and lines[:1] in [' ', '\t']:
285 if not self._buffer and lines[:1] in [' ', '\t']:
287 lines = 'if 1:\n%s' % lines
286 lines = 'if 1:\n%s' % lines
288
287
289 self._store(lines)
288 self._store(lines)
290 source = self.source
289 source = self.source
291
290
292 # Before calling _compile(), reset the code object to None so that if an
291 # Before calling _compile(), reset the code object to None so that if an
293 # exception is raised in compilation, we don't mislead by having
292 # exception is raised in compilation, we don't mislead by having
294 # inconsistent code/source attributes.
293 # inconsistent code/source attributes.
295 self.code, self._is_complete = None, None
294 self.code, self._is_complete = None, None
296
295
297 self._update_indent(lines)
296 self._update_indent(lines)
298 try:
297 try:
299 self.code = self._compile(source)
298 self.code = self._compile(source)
300 # Invalid syntax can produce any of a number of different errors from
299 # Invalid syntax can produce any of a number of different errors from
301 # inside the compiler, so we have to catch them all. Syntax errors
300 # inside the compiler, so we have to catch them all. Syntax errors
302 # immediately produce a 'ready' block, so the invalid Python can be
301 # immediately produce a 'ready' block, so the invalid Python can be
303 # sent to the kernel for evaluation with possible ipython
302 # sent to the kernel for evaluation with possible ipython
304 # special-syntax conversion.
303 # special-syntax conversion.
305 except (SyntaxError, OverflowError, ValueError, TypeError,
304 except (SyntaxError, OverflowError, ValueError, TypeError,
306 MemoryError):
305 MemoryError):
307 self._is_complete = True
306 self._is_complete = True
308 else:
307 else:
309 # Compilation didn't produce any exceptions (though it may not have
308 # Compilation didn't produce any exceptions (though it may not have
310 # given a complete code object)
309 # given a complete code object)
311 self._is_complete = self.code is not None
310 self._is_complete = self.code is not None
312
311
313 return self._is_complete
312 return self._is_complete
314
313
315 def push_accepts_more(self):
314 def push_accepts_more(self):
316 """Return whether a block of interactive input can accept more input.
315 """Return whether a block of interactive input can accept more input.
317
316
318 This method is meant to be used by line-oriented frontends, who need to
317 This method is meant to be used by line-oriented frontends, who need to
319 guess whether a block is complete or not based solely on prior and
318 guess whether a block is complete or not based solely on prior and
320 current input lines. The InputSplitter considers it has a complete
319 current input lines. The InputSplitter considers it has a complete
321 interactive block and will not accept more input only when either a
320 interactive block and will not accept more input only when either a
322 SyntaxError is raised, or *all* of the following are true:
321 SyntaxError is raised, or *all* of the following are true:
323
322
324 1. The input compiles to a complete statement.
323 1. The input compiles to a complete statement.
325
324
326 2. The indentation level is flush-left (because if we are indented,
325 2. The indentation level is flush-left (because if we are indented,
327 like inside a function definition or for loop, we need to keep
326 like inside a function definition or for loop, we need to keep
328 reading new input).
327 reading new input).
329
328
330 3. There is one extra line consisting only of whitespace.
329 3. There is one extra line consisting only of whitespace.
331
330
332 Because of condition #3, this method should be used only by
331 Because of condition #3, this method should be used only by
333 *line-oriented* frontends, since it means that intermediate blank lines
332 *line-oriented* frontends, since it means that intermediate blank lines
334 are not allowed in function definitions (or any other indented block).
333 are not allowed in function definitions (or any other indented block).
335
334
336 Block-oriented frontends that have a separate keyboard event to
335 Block-oriented frontends that have a separate keyboard event to
337 indicate execution should use the :meth:`split_blocks` method instead.
336 indicate execution should use the :meth:`split_blocks` method instead.
338
337
339 If the current input produces a syntax error, this method immediately
338 If the current input produces a syntax error, this method immediately
340 returns False but does *not* raise the syntax error exception, as
339 returns False but does *not* raise the syntax error exception, as
341 typically clients will want to send invalid syntax to an execution
340 typically clients will want to send invalid syntax to an execution
342 backend which might convert the invalid syntax into valid Python via
341 backend which might convert the invalid syntax into valid Python via
343 one of the dynamic IPython mechanisms.
342 one of the dynamic IPython mechanisms.
344 """
343 """
345
344
346 if not self._is_complete:
345 if not self._is_complete:
347 return True
346 return True
348
347
349 if self.indent_spaces==0:
348 if self.indent_spaces==0:
350 return False
349 return False
351
350
352 last_line = self.source.splitlines()[-1]
351 last_line = self.source.splitlines()[-1]
353 return bool(last_line and not last_line.isspace())
352 return bool(last_line and not last_line.isspace())
354
353
355 def split_blocks(self, lines):
354 def split_blocks(self, lines):
356 """Split a multiline string into multiple input blocks.
355 """Split a multiline string into multiple input blocks.
357
356
358 Note: this method starts by performing a full reset().
357 Note: this method starts by performing a full reset().
359
358
360 Parameters
359 Parameters
361 ----------
360 ----------
362 lines : str
361 lines : str
363 A possibly multiline string.
362 A possibly multiline string.
364
363
365 Returns
364 Returns
366 -------
365 -------
367 blocks : list
366 blocks : list
368 A list of strings, each possibly multiline. Each string corresponds
367 A list of strings, each possibly multiline. Each string corresponds
369 to a single block that can be compiled in 'single' mode (unless it
368 to a single block that can be compiled in 'single' mode (unless it
370 has a syntax error)."""
369 has a syntax error)."""
371
370
372 # This code is fairly delicate. If you make any changes here, make
371 # This code is fairly delicate. If you make any changes here, make
373 # absolutely sure that you do run the full test suite and ALL tests
372 # absolutely sure that you do run the full test suite and ALL tests
374 # pass.
373 # pass.
375
374
376 self.reset()
375 self.reset()
377 blocks = []
376 blocks = []
378
377
379 # Reversed copy so we can use pop() efficiently and consume the input
378 # Reversed copy so we can use pop() efficiently and consume the input
380 # as a stack
379 # as a stack
381 lines = lines.splitlines()[::-1]
380 lines = lines.splitlines()[::-1]
382 # Outer loop over all input
381 # Outer loop over all input
383 while lines:
382 while lines:
384 # Inner loop to build each block
383 # Inner loop to build each block
385 while True:
384 while True:
386 # Safety exit from inner loop
385 # Safety exit from inner loop
387 if not lines:
386 if not lines:
388 break
387 break
389 # Grab next line but don't push it yet
388 # Grab next line but don't push it yet
390 next_line = lines.pop()
389 next_line = lines.pop()
391 # Blank/empty lines are pushed as-is
390 # Blank/empty lines are pushed as-is
392 if not next_line or next_line.isspace():
391 if not next_line or next_line.isspace():
393 self.push(next_line)
392 self.push(next_line)
394 continue
393 continue
395
394
396 # Check indentation changes caused by the *next* line
395 # Check indentation changes caused by the *next* line
397 indent_spaces, _full_dedent = self._find_indent(next_line)
396 indent_spaces, _full_dedent = self._find_indent(next_line)
398
397
399 # If the next line causes a dedent, it can be for two differnt
398 # If the next line causes a dedent, it can be for two differnt
400 # reasons: either an explicit de-dent by the user or a
399 # reasons: either an explicit de-dent by the user or a
401 # return/raise/pass statement. These MUST be handled
400 # return/raise/pass statement. These MUST be handled
402 # separately:
401 # separately:
403 #
402 #
404 # 1. the first case is only detected when the actual explicit
403 # 1. the first case is only detected when the actual explicit
405 # dedent happens, and that would be the *first* line of a *new*
404 # dedent happens, and that would be the *first* line of a *new*
406 # block. Thus, we must put the line back into the input buffer
405 # block. Thus, we must put the line back into the input buffer
407 # so that it starts a new block on the next pass.
406 # so that it starts a new block on the next pass.
408 #
407 #
409 # 2. the second case is detected in the line before the actual
408 # 2. the second case is detected in the line before the actual
410 # dedent happens, so , we consume the line and we can break out
409 # dedent happens, so , we consume the line and we can break out
411 # to start a new block.
410 # to start a new block.
412
411
413 # Case 1, explicit dedent causes a break
412 # Case 1, explicit dedent causes a break
414 if _full_dedent and not next_line.startswith(' '):
413 if _full_dedent and not next_line.startswith(' '):
415 lines.append(next_line)
414 lines.append(next_line)
416 break
415 break
417
416
418 # Otherwise any line is pushed
417 # Otherwise any line is pushed
419 self.push(next_line)
418 self.push(next_line)
420
419
421 # Case 2, full dedent with full block ready:
420 # Case 2, full dedent with full block ready:
422 if _full_dedent or \
421 if _full_dedent or \
423 self.indent_spaces==0 and not self.push_accepts_more():
422 self.indent_spaces==0 and not self.push_accepts_more():
424 break
423 break
425 # Form the new block with the current source input
424 # Form the new block with the current source input
426 blocks.append(self.source_reset())
425 blocks.append(self.source_reset())
427
426
428 return blocks
427 return blocks
429
428
430 #------------------------------------------------------------------------
429 #------------------------------------------------------------------------
431 # Private interface
430 # Private interface
432 #------------------------------------------------------------------------
431 #------------------------------------------------------------------------
433
432
434 def _find_indent(self, line):
433 def _find_indent(self, line):
435 """Compute the new indentation level for a single line.
434 """Compute the new indentation level for a single line.
436
435
437 Parameters
436 Parameters
438 ----------
437 ----------
439 line : str
438 line : str
440 A single new line of non-whitespace, non-comment Python input.
439 A single new line of non-whitespace, non-comment Python input.
441
440
442 Returns
441 Returns
443 -------
442 -------
444 indent_spaces : int
443 indent_spaces : int
445 New value for the indent level (it may be equal to self.indent_spaces
444 New value for the indent level (it may be equal to self.indent_spaces
446 if indentation doesn't change.
445 if indentation doesn't change.
447
446
448 full_dedent : boolean
447 full_dedent : boolean
449 Whether the new line causes a full flush-left dedent.
448 Whether the new line causes a full flush-left dedent.
450 """
449 """
451 indent_spaces = self.indent_spaces
450 indent_spaces = self.indent_spaces
452 full_dedent = self._full_dedent
451 full_dedent = self._full_dedent
453
452
454 inisp = num_ini_spaces(line)
453 inisp = num_ini_spaces(line)
455 if inisp < indent_spaces:
454 if inisp < indent_spaces:
456 indent_spaces = inisp
455 indent_spaces = inisp
457 if indent_spaces <= 0:
456 if indent_spaces <= 0:
458 #print 'Full dedent in text',self.source # dbg
457 #print 'Full dedent in text',self.source # dbg
459 full_dedent = True
458 full_dedent = True
460
459
461 if line[-1] == ':':
460 if line[-1] == ':':
462 indent_spaces += 4
461 indent_spaces += 4
463 elif dedent_re.match(line):
462 elif dedent_re.match(line):
464 indent_spaces -= 4
463 indent_spaces -= 4
465 if indent_spaces <= 0:
464 if indent_spaces <= 0:
466 full_dedent = True
465 full_dedent = True
467
466
468 # Safety
467 # Safety
469 if indent_spaces < 0:
468 if indent_spaces < 0:
470 indent_spaces = 0
469 indent_spaces = 0
471 #print 'safety' # dbg
470 #print 'safety' # dbg
472
471
473 return indent_spaces, full_dedent
472 return indent_spaces, full_dedent
474
473
475 def _update_indent(self, lines):
474 def _update_indent(self, lines):
476 for line in remove_comments(lines).splitlines():
475 for line in remove_comments(lines).splitlines():
477 if line and not line.isspace():
476 if line and not line.isspace():
478 self.indent_spaces, self._full_dedent = self._find_indent(line)
477 self.indent_spaces, self._full_dedent = self._find_indent(line)
479
478
480 def _store(self, lines):
479 def _store(self, lines):
481 """Store one or more lines of input.
480 """Store one or more lines of input.
482
481
483 If input lines are not newline-terminated, a newline is automatically
482 If input lines are not newline-terminated, a newline is automatically
484 appended."""
483 appended."""
485
484
486 if lines.endswith('\n'):
485 if lines.endswith('\n'):
487 self._buffer.append(lines)
486 self._buffer.append(lines)
488 else:
487 else:
489 self._buffer.append(lines+'\n')
488 self._buffer.append(lines+'\n')
490 self._set_source()
489 self._set_source()
491
490
492 def _set_source(self):
491 def _set_source(self):
493 self.source = ''.join(self._buffer).encode(self.encoding)
492 self.source = ''.join(self._buffer).encode(self.encoding)
494
493
495
494
496 #-----------------------------------------------------------------------------
495 #-----------------------------------------------------------------------------
497 # Functions and classes for IPython-specific syntactic support
496 # Functions and classes for IPython-specific syntactic support
498 #-----------------------------------------------------------------------------
497 #-----------------------------------------------------------------------------
499
498
500 # RegExp for splitting line contents into pre-char//first word-method//rest.
499 # RegExp for splitting line contents into pre-char//first word-method//rest.
501 # For clarity, each group in on one line.
500 # For clarity, each group in on one line.
502
501
503 line_split = re.compile("""
502 line_split = re.compile("""
504 ^(\s*) # any leading space
503 ^(\s*) # any leading space
505 ([,;/%]|!!?|\?\??) # escape character or characters
504 ([,;/%]|!!?|\?\??) # escape character or characters
506 \s*([\w\.]*) # function/method part (mix of \w and '.')
505 \s*([\w\.]*) # function/method part (mix of \w and '.')
507 (\s+.*$|$) # rest of line
506 (\s+.*$|$) # rest of line
508 """, re.VERBOSE)
507 """, re.VERBOSE)
509
508
510
509
511 def split_user_input(line):
510 def split_user_input(line):
512 """Split user input into early whitespace, esc-char, function part and rest.
511 """Split user input into early whitespace, esc-char, function part and rest.
513
512
514 This is currently handles lines with '=' in them in a very inconsistent
513 This is currently handles lines with '=' in them in a very inconsistent
515 manner.
514 manner.
516
515
517 Examples
516 Examples
518 ========
517 ========
519 >>> split_user_input('x=1')
518 >>> split_user_input('x=1')
520 ('', '', 'x=1', '')
519 ('', '', 'x=1', '')
521 >>> split_user_input('?')
520 >>> split_user_input('?')
522 ('', '?', '', '')
521 ('', '?', '', '')
523 >>> split_user_input('??')
522 >>> split_user_input('??')
524 ('', '??', '', '')
523 ('', '??', '', '')
525 >>> split_user_input(' ?')
524 >>> split_user_input(' ?')
526 (' ', '?', '', '')
525 (' ', '?', '', '')
527 >>> split_user_input(' ??')
526 >>> split_user_input(' ??')
528 (' ', '??', '', '')
527 (' ', '??', '', '')
529 >>> split_user_input('??x')
528 >>> split_user_input('??x')
530 ('', '??', 'x', '')
529 ('', '??', 'x', '')
531 >>> split_user_input('?x=1')
530 >>> split_user_input('?x=1')
532 ('', '', '?x=1', '')
531 ('', '', '?x=1', '')
533 >>> split_user_input('!ls')
532 >>> split_user_input('!ls')
534 ('', '!', 'ls', '')
533 ('', '!', 'ls', '')
535 >>> split_user_input(' !ls')
534 >>> split_user_input(' !ls')
536 (' ', '!', 'ls', '')
535 (' ', '!', 'ls', '')
537 >>> split_user_input('!!ls')
536 >>> split_user_input('!!ls')
538 ('', '!!', 'ls', '')
537 ('', '!!', 'ls', '')
539 >>> split_user_input(' !!ls')
538 >>> split_user_input(' !!ls')
540 (' ', '!!', 'ls', '')
539 (' ', '!!', 'ls', '')
541 >>> split_user_input(',ls')
540 >>> split_user_input(',ls')
542 ('', ',', 'ls', '')
541 ('', ',', 'ls', '')
543 >>> split_user_input(';ls')
542 >>> split_user_input(';ls')
544 ('', ';', 'ls', '')
543 ('', ';', 'ls', '')
545 >>> split_user_input(' ;ls')
544 >>> split_user_input(' ;ls')
546 (' ', ';', 'ls', '')
545 (' ', ';', 'ls', '')
547 >>> split_user_input('f.g(x)')
546 >>> split_user_input('f.g(x)')
548 ('', '', 'f.g(x)', '')
547 ('', '', 'f.g(x)', '')
549 >>> split_user_input('f.g (x)')
548 >>> split_user_input('f.g (x)')
550 ('', '', 'f.g', '(x)')
549 ('', '', 'f.g', '(x)')
551 """
550 """
552 match = line_split.match(line)
551 match = line_split.match(line)
553 if match:
552 if match:
554 lspace, esc, fpart, rest = match.groups()
553 lspace, esc, fpart, rest = match.groups()
555 else:
554 else:
556 # print "match failed for line '%s'" % line
555 # print "match failed for line '%s'" % line
557 try:
556 try:
558 fpart, rest = line.split(None, 1)
557 fpart, rest = line.split(None, 1)
559 except ValueError:
558 except ValueError:
560 # print "split failed for line '%s'" % line
559 # print "split failed for line '%s'" % line
561 fpart, rest = line,''
560 fpart, rest = line,''
562 lspace = re.match('^(\s*)(.*)', line).groups()[0]
561 lspace = re.match('^(\s*)(.*)', line).groups()[0]
563 esc = ''
562 esc = ''
564
563
565 # fpart has to be a valid python identifier, so it better be only pure
564 # fpart has to be a valid python identifier, so it better be only pure
566 # ascii, no unicode:
565 # ascii, no unicode:
567 try:
566 try:
568 fpart = fpart.encode('ascii')
567 fpart = fpart.encode('ascii')
569 except UnicodeEncodeError:
568 except UnicodeEncodeError:
570 lspace = unicode(lspace)
569 lspace = unicode(lspace)
571 rest = fpart + u' ' + rest
570 rest = fpart + u' ' + rest
572 fpart = u''
571 fpart = u''
573
572
574 #print 'line:<%s>' % line # dbg
573 #print 'line:<%s>' % line # dbg
575 #print 'esc <%s> fpart <%s> rest <%s>' % (esc,fpart.strip(),rest) # dbg
574 #print 'esc <%s> fpart <%s> rest <%s>' % (esc,fpart.strip(),rest) # dbg
576 return lspace, esc, fpart.strip(), rest.lstrip()
575 return lspace, esc, fpart.strip(), rest.lstrip()
577
576
578
577
579 # The escaped translators ALL receive a line where their own escape has been
578 # The escaped translators ALL receive a line where their own escape has been
580 # stripped. Only '?' is valid at the end of the line, all others can only be
579 # stripped. Only '?' is valid at the end of the line, all others can only be
581 # placed at the start.
580 # placed at the start.
582
581
583 class LineInfo(object):
582 class LineInfo(object):
584 """A single line of input and associated info.
583 """A single line of input and associated info.
585
584
586 This is a utility class that mostly wraps the output of
585 This is a utility class that mostly wraps the output of
587 :func:`split_user_input` into a convenient object to be passed around
586 :func:`split_user_input` into a convenient object to be passed around
588 during input transformations.
587 during input transformations.
589
588
590 Includes the following as properties:
589 Includes the following as properties:
591
590
592 line
591 line
593 The original, raw line
592 The original, raw line
594
593
595 lspace
594 lspace
596 Any early whitespace before actual text starts.
595 Any early whitespace before actual text starts.
597
596
598 esc
597 esc
599 The initial esc character (or characters, for double-char escapes like
598 The initial esc character (or characters, for double-char escapes like
600 '??' or '!!').
599 '??' or '!!').
601
600
602 fpart
601 fpart
603 The 'function part', which is basically the maximal initial sequence
602 The 'function part', which is basically the maximal initial sequence
604 of valid python identifiers and the '.' character. This is what is
603 of valid python identifiers and the '.' character. This is what is
605 checked for alias and magic transformations, used for auto-calling,
604 checked for alias and magic transformations, used for auto-calling,
606 etc.
605 etc.
607
606
608 rest
607 rest
609 Everything else on the line.
608 Everything else on the line.
610 """
609 """
611 def __init__(self, line):
610 def __init__(self, line):
612 self.line = line
611 self.line = line
613 self.lspace, self.esc, self.fpart, self.rest = \
612 self.lspace, self.esc, self.fpart, self.rest = \
614 split_user_input(line)
613 split_user_input(line)
615
614
616 def __str__(self):
615 def __str__(self):
617 return "LineInfo [%s|%s|%s|%s]" % (self.lspace, self.esc,
616 return "LineInfo [%s|%s|%s|%s]" % (self.lspace, self.esc,
618 self.fpart, self.rest)
617 self.fpart, self.rest)
619
618
620
619
621 # Transformations of the special syntaxes that don't rely on an explicit escape
620 # Transformations of the special syntaxes that don't rely on an explicit escape
622 # character but instead on patterns on the input line
621 # character but instead on patterns on the input line
623
622
624 # The core transformations are implemented as standalone functions that can be
623 # The core transformations are implemented as standalone functions that can be
625 # tested and validated in isolation. Each of these uses a regexp, we
624 # tested and validated in isolation. Each of these uses a regexp, we
626 # pre-compile these and keep them close to each function definition for clarity
625 # pre-compile these and keep them close to each function definition for clarity
627
626
628 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
627 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
629 r'\s*=\s*!\s*(?P<cmd>.*)')
628 r'\s*=\s*!\s*(?P<cmd>.*)')
630
629
631 def transform_assign_system(line):
630 def transform_assign_system(line):
632 """Handle the `files = !ls` syntax."""
631 """Handle the `files = !ls` syntax."""
633 # FIXME: This transforms the line to use %sc, but we've listed that magic
632 # FIXME: This transforms the line to use %sc, but we've listed that magic
634 # as deprecated. We should then implement this functionality in a
633 # as deprecated. We should then implement this functionality in a
635 # standalone api that we can transform to, without going through a
634 # standalone api that we can transform to, without going through a
636 # deprecated magic.
635 # deprecated magic.
637 m = _assign_system_re.match(line)
636 m = _assign_system_re.match(line)
638 if m is not None:
637 if m is not None:
639 cmd = m.group('cmd')
638 cmd = m.group('cmd')
640 lhs = m.group('lhs')
639 lhs = m.group('lhs')
641 expr = make_quoted_expr("sc -l = %s" % cmd)
640 expr = make_quoted_expr("sc -l = %s" % cmd)
642 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
641 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
643 return new_line
642 return new_line
644 return line
643 return line
645
644
646
645
647 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
646 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
648 r'\s*=\s*%\s*(?P<cmd>.*)')
647 r'\s*=\s*%\s*(?P<cmd>.*)')
649
648
650 def transform_assign_magic(line):
649 def transform_assign_magic(line):
651 """Handle the `a = %who` syntax."""
650 """Handle the `a = %who` syntax."""
652 m = _assign_magic_re.match(line)
651 m = _assign_magic_re.match(line)
653 if m is not None:
652 if m is not None:
654 cmd = m.group('cmd')
653 cmd = m.group('cmd')
655 lhs = m.group('lhs')
654 lhs = m.group('lhs')
656 expr = make_quoted_expr(cmd)
655 expr = make_quoted_expr(cmd)
657 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
656 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
658 return new_line
657 return new_line
659 return line
658 return line
660
659
661
660
662 _classic_prompt_re = re.compile(r'^([ \t]*>>> |^[ \t]*\.\.\. )')
661 _classic_prompt_re = re.compile(r'^([ \t]*>>> |^[ \t]*\.\.\. )')
663
662
664 def transform_classic_prompt(line):
663 def transform_classic_prompt(line):
665 """Handle inputs that start with '>>> ' syntax."""
664 """Handle inputs that start with '>>> ' syntax."""
666
665
667 if not line or line.isspace():
666 if not line or line.isspace():
668 return line
667 return line
669 m = _classic_prompt_re.match(line)
668 m = _classic_prompt_re.match(line)
670 if m:
669 if m:
671 return line[len(m.group(0)):]
670 return line[len(m.group(0)):]
672 else:
671 else:
673 return line
672 return line
674
673
675
674
676 _ipy_prompt_re = re.compile(r'^([ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )')
675 _ipy_prompt_re = re.compile(r'^([ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )')
677
676
678 def transform_ipy_prompt(line):
677 def transform_ipy_prompt(line):
679 """Handle inputs that start classic IPython prompt syntax."""
678 """Handle inputs that start classic IPython prompt syntax."""
680
679
681 if not line or line.isspace():
680 if not line or line.isspace():
682 return line
681 return line
682 #print 'LINE: %r' % line # dbg
683 m = _ipy_prompt_re.match(line)
683 m = _ipy_prompt_re.match(line)
684 if m:
684 if m:
685 #print 'MATCH! %r -> %r' % (line, line[len(m.group(0)):]) # dbg
685 return line[len(m.group(0)):]
686 return line[len(m.group(0)):]
686 else:
687 else:
687 return line
688 return line
688
689
689
690
690 class EscapedTransformer(object):
691 class EscapedTransformer(object):
691 """Class to transform lines that are explicitly escaped out."""
692 """Class to transform lines that are explicitly escaped out."""
692
693
693 def __init__(self):
694 def __init__(self):
694 tr = { ESC_SHELL : self._tr_system,
695 tr = { ESC_SHELL : self._tr_system,
695 ESC_SH_CAP : self._tr_system2,
696 ESC_SH_CAP : self._tr_system2,
696 ESC_HELP : self._tr_help,
697 ESC_HELP : self._tr_help,
697 ESC_HELP2 : self._tr_help,
698 ESC_HELP2 : self._tr_help,
698 ESC_MAGIC : self._tr_magic,
699 ESC_MAGIC : self._tr_magic,
699 ESC_QUOTE : self._tr_quote,
700 ESC_QUOTE : self._tr_quote,
700 ESC_QUOTE2 : self._tr_quote2,
701 ESC_QUOTE2 : self._tr_quote2,
701 ESC_PAREN : self._tr_paren }
702 ESC_PAREN : self._tr_paren }
702 self.tr = tr
703 self.tr = tr
703
704
704 # Support for syntax transformations that use explicit escapes typed by the
705 # Support for syntax transformations that use explicit escapes typed by the
705 # user at the beginning of a line
706 # user at the beginning of a line
706 @staticmethod
707 @staticmethod
707 def _tr_system(line_info):
708 def _tr_system(line_info):
708 "Translate lines escaped with: !"
709 "Translate lines escaped with: !"
709 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
710 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
710 return '%sget_ipython().system(%s)' % (line_info.lspace,
711 return '%sget_ipython().system(%s)' % (line_info.lspace,
711 make_quoted_expr(cmd))
712 make_quoted_expr(cmd))
712
713
713 @staticmethod
714 @staticmethod
714 def _tr_system2(line_info):
715 def _tr_system2(line_info):
715 "Translate lines escaped with: !!"
716 "Translate lines escaped with: !!"
716 cmd = line_info.line.lstrip()[2:]
717 cmd = line_info.line.lstrip()[2:]
717 return '%sget_ipython().getoutput(%s)' % (line_info.lspace,
718 return '%sget_ipython().getoutput(%s)' % (line_info.lspace,
718 make_quoted_expr(cmd))
719 make_quoted_expr(cmd))
719
720
720 @staticmethod
721 @staticmethod
721 def _tr_help(line_info):
722 def _tr_help(line_info):
722 "Translate lines escaped with: ?/??"
723 "Translate lines escaped with: ?/??"
723 # A naked help line should just fire the intro help screen
724 # A naked help line should just fire the intro help screen
724 if not line_info.line[1:]:
725 if not line_info.line[1:]:
725 return 'get_ipython().show_usage()'
726 return 'get_ipython().show_usage()'
726
727
727 # There may be one or two '?' at the end, move them to the front so that
728 # There may be one or two '?' at the end, move them to the front so that
728 # the rest of the logic can assume escapes are at the start
729 # the rest of the logic can assume escapes are at the start
729 line = line_info.line
730 line = line_info.line
730 if line.endswith('?'):
731 if line.endswith('?'):
731 line = line[-1] + line[:-1]
732 line = line[-1] + line[:-1]
732 if line.endswith('?'):
733 if line.endswith('?'):
733 line = line[-1] + line[:-1]
734 line = line[-1] + line[:-1]
734 line_info = LineInfo(line)
735 line_info = LineInfo(line)
735
736
736 # From here on, simply choose which level of detail to get.
737 # From here on, simply choose which level of detail to get.
737 if line_info.esc == '?':
738 if line_info.esc == '?':
738 pinfo = 'pinfo'
739 pinfo = 'pinfo'
739 elif line_info.esc == '??':
740 elif line_info.esc == '??':
740 pinfo = 'pinfo2'
741 pinfo = 'pinfo2'
741
742
742 tpl = '%sget_ipython().magic("%s %s")'
743 tpl = '%sget_ipython().magic("%s %s")'
743 return tpl % (line_info.lspace, pinfo,
744 return tpl % (line_info.lspace, pinfo,
744 ' '.join([line_info.fpart, line_info.rest]).strip())
745 ' '.join([line_info.fpart, line_info.rest]).strip())
745
746
746 @staticmethod
747 @staticmethod
747 def _tr_magic(line_info):
748 def _tr_magic(line_info):
748 "Translate lines escaped with: %"
749 "Translate lines escaped with: %"
749 tpl = '%sget_ipython().magic(%s)'
750 tpl = '%sget_ipython().magic(%s)'
750 cmd = make_quoted_expr(' '.join([line_info.fpart,
751 cmd = make_quoted_expr(' '.join([line_info.fpart,
751 line_info.rest]).strip())
752 line_info.rest]).strip())
752 return tpl % (line_info.lspace, cmd)
753 return tpl % (line_info.lspace, cmd)
753
754
754 @staticmethod
755 @staticmethod
755 def _tr_quote(line_info):
756 def _tr_quote(line_info):
756 "Translate lines escaped with: ,"
757 "Translate lines escaped with: ,"
757 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
758 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
758 '", "'.join(line_info.rest.split()) )
759 '", "'.join(line_info.rest.split()) )
759
760
760 @staticmethod
761 @staticmethod
761 def _tr_quote2(line_info):
762 def _tr_quote2(line_info):
762 "Translate lines escaped with: ;"
763 "Translate lines escaped with: ;"
763 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
764 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
764 line_info.rest)
765 line_info.rest)
765
766
766 @staticmethod
767 @staticmethod
767 def _tr_paren(line_info):
768 def _tr_paren(line_info):
768 "Translate lines escaped with: /"
769 "Translate lines escaped with: /"
769 return '%s%s(%s)' % (line_info.lspace, line_info.fpart,
770 return '%s%s(%s)' % (line_info.lspace, line_info.fpart,
770 ", ".join(line_info.rest.split()))
771 ", ".join(line_info.rest.split()))
771
772
772 def __call__(self, line):
773 def __call__(self, line):
773 """Class to transform lines that are explicitly escaped out.
774 """Class to transform lines that are explicitly escaped out.
774
775
775 This calls the above _tr_* static methods for the actual line
776 This calls the above _tr_* static methods for the actual line
776 translations."""
777 translations."""
777
778
778 # Empty lines just get returned unmodified
779 # Empty lines just get returned unmodified
779 if not line or line.isspace():
780 if not line or line.isspace():
780 return line
781 return line
781
782
782 # Get line endpoints, where the escapes can be
783 # Get line endpoints, where the escapes can be
783 line_info = LineInfo(line)
784 line_info = LineInfo(line)
784
785
785 # If the escape is not at the start, only '?' needs to be special-cased.
786 # If the escape is not at the start, only '?' needs to be special-cased.
786 # All other escapes are only valid at the start
787 # All other escapes are only valid at the start
787 if not line_info.esc in self.tr:
788 if not line_info.esc in self.tr:
788 if line.endswith(ESC_HELP):
789 if line.endswith(ESC_HELP):
789 return self._tr_help(line_info)
790 return self._tr_help(line_info)
790 else:
791 else:
791 # If we don't recognize the escape, don't modify the line
792 # If we don't recognize the escape, don't modify the line
792 return line
793 return line
793
794
794 return self.tr[line_info.esc](line_info)
795 return self.tr[line_info.esc](line_info)
795
796
796
797
797 # A function-looking object to be used by the rest of the code. The purpose of
798 # A function-looking object to be used by the rest of the code. The purpose of
798 # the class in this case is to organize related functionality, more than to
799 # the class in this case is to organize related functionality, more than to
799 # manage state.
800 # manage state.
800 transform_escaped = EscapedTransformer()
801 transform_escaped = EscapedTransformer()
801
802
802
803
803 class IPythonInputSplitter(InputSplitter):
804 class IPythonInputSplitter(InputSplitter):
804 """An input splitter that recognizes all of IPython's special syntax."""
805 """An input splitter that recognizes all of IPython's special syntax."""
805
806
806 def push(self, lines):
807 def push(self, lines):
807 """Push one or more lines of IPython input.
808 """Push one or more lines of IPython input.
808 """
809 """
809 if not lines:
810 if not lines:
810 return super(IPythonInputSplitter, self).push(lines)
811 return super(IPythonInputSplitter, self).push(lines)
811
812
812 lines_list = lines.splitlines()
813 lines_list = lines.splitlines()
813
814
814 transforms = [transform_escaped, transform_assign_system,
815 transforms = [transform_escaped, transform_assign_system,
815 transform_assign_magic, transform_ipy_prompt,
816 transform_assign_magic, transform_ipy_prompt,
816 transform_classic_prompt]
817 transform_classic_prompt]
817
818
818 # Transform logic
819 # Transform logic
819 #
820 #
820 # We only apply the line transformers to the input if we have either no
821 # We only apply the line transformers to the input if we have either no
821 # input yet, or complete input, or if the last line of the buffer ends
822 # input yet, or complete input, or if the last line of the buffer ends
822 # with ':' (opening an indented block). This prevents the accidental
823 # with ':' (opening an indented block). This prevents the accidental
823 # transformation of escapes inside multiline expressions like
824 # transformation of escapes inside multiline expressions like
824 # triple-quoted strings or parenthesized expressions.
825 # triple-quoted strings or parenthesized expressions.
825 #
826 #
826 # The last heuristic, while ugly, ensures that the first line of an
827 # The last heuristic, while ugly, ensures that the first line of an
827 # indented block is correctly transformed.
828 # indented block is correctly transformed.
828 #
829 #
829 # FIXME: try to find a cleaner approach for this last bit.
830 # FIXME: try to find a cleaner approach for this last bit.
830
831
832 # If we were in 'replace' mode, since we're going to pump the parent
833 # 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
835 # by one. Note that this only matters if the input has more than one
836 # line.
837 changed_input_mode = False
838
839 if len(lines_list)>1 and self.input_mode == 'replace':
840 self.reset()
841 changed_input_mode = True
842 saved_input_mode = 'replace'
843 self.input_mode = 'append'
844
845 try:
846 push = super(IPythonInputSplitter, self).push
831 for line in lines_list:
847 for line in lines_list:
832 if self._is_complete or not self._buffer or \
848 if self._is_complete or not self._buffer or \
833 (self._buffer and self._buffer[-1].rstrip().endswith(':')):
849 (self._buffer and self._buffer[-1].rstrip().endswith(':')):
834 for f in transforms:
850 for f in transforms:
835 line = f(line)
851 line = f(line)
836
852
837 out = super(IPythonInputSplitter, self).push(line)
853 out = push(line)
854 finally:
855 if changed_input_mode:
856 self.input_mode = saved_input_mode
838
857
839 return out
858 return out
@@ -1,622 +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 = 'replace'
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 ],
416 ],
416
417
417 ipy_prompt =
418 ipy_prompt =
418 [('In [1]: x=1', 'x=1'),
419 [('In [1]: x=1', 'x=1'),
419 ('x=1', 'x=1'), # normal input is unmodified
420 ('x=1', 'x=1'), # normal input is unmodified
420 (' ',' '), # blank lines are kept intact
421 (' ',' '), # blank lines are kept intact
422 (' ....: ', ''), # continuation prompts
421 ],
423 ],
422
424
423 # Tests for the escape transformer to leave normal code alone
425 # Tests for the escape transformer to leave normal code alone
424 escaped_noesc =
426 escaped_noesc =
425 [ (' ', ' '),
427 [ (' ', ' '),
426 ('x=1', 'x=1'),
428 ('x=1', 'x=1'),
427 ],
429 ],
428
430
429 # System calls
431 # System calls
430 escaped_shell =
432 escaped_shell =
431 [ ('!ls', 'get_ipython().system("ls")'),
433 [ ('!ls', 'get_ipython().system("ls")'),
432 # Double-escape shell, this means to capture the output of the
434 # Double-escape shell, this means to capture the output of the
433 # subprocess and return it
435 # subprocess and return it
434 ('!!ls', 'get_ipython().getoutput("ls")'),
436 ('!!ls', 'get_ipython().getoutput("ls")'),
435 ],
437 ],
436
438
437 # Help/object info
439 # Help/object info
438 escaped_help =
440 escaped_help =
439 [ ('?', 'get_ipython().show_usage()'),
441 [ ('?', 'get_ipython().show_usage()'),
440 ('?x1', 'get_ipython().magic("pinfo x1")'),
442 ('?x1', 'get_ipython().magic("pinfo x1")'),
441 ('??x2', 'get_ipython().magic("pinfo2 x2")'),
443 ('??x2', 'get_ipython().magic("pinfo2 x2")'),
442 ('x3?', 'get_ipython().magic("pinfo x3")'),
444 ('x3?', 'get_ipython().magic("pinfo x3")'),
443 ('x4??', 'get_ipython().magic("pinfo2 x4")'),
445 ('x4??', 'get_ipython().magic("pinfo2 x4")'),
444 ],
446 ],
445
447
446 # Explicit magic calls
448 # Explicit magic calls
447 escaped_magic =
449 escaped_magic =
448 [ ('%cd', 'get_ipython().magic("cd")'),
450 [ ('%cd', 'get_ipython().magic("cd")'),
449 ('%cd /home', 'get_ipython().magic("cd /home")'),
451 ('%cd /home', 'get_ipython().magic("cd /home")'),
450 (' %magic', ' get_ipython().magic("magic")'),
452 (' %magic', ' get_ipython().magic("magic")'),
451 ],
453 ],
452
454
453 # Quoting with separate arguments
455 # Quoting with separate arguments
454 escaped_quote =
456 escaped_quote =
455 [ (',f', 'f("")'),
457 [ (',f', 'f("")'),
456 (',f x', 'f("x")'),
458 (',f x', 'f("x")'),
457 (' ,f y', ' f("y")'),
459 (' ,f y', ' f("y")'),
458 (',f a b', 'f("a", "b")'),
460 (',f a b', 'f("a", "b")'),
459 ],
461 ],
460
462
461 # Quoting with single argument
463 # Quoting with single argument
462 escaped_quote2 =
464 escaped_quote2 =
463 [ (';f', 'f("")'),
465 [ (';f', 'f("")'),
464 (';f x', 'f("x")'),
466 (';f x', 'f("x")'),
465 (' ;f y', ' f("y")'),
467 (' ;f y', ' f("y")'),
466 (';f a b', 'f("a b")'),
468 (';f a b', 'f("a b")'),
467 ],
469 ],
468
470
469 # Simply apply parens
471 # Simply apply parens
470 escaped_paren =
472 escaped_paren =
471 [ ('/f', 'f()'),
473 [ ('/f', 'f()'),
472 ('/f x', 'f(x)'),
474 ('/f x', 'f(x)'),
473 (' /f y', ' f(y)'),
475 (' /f y', ' f(y)'),
474 ('/f a b', 'f(a, b)'),
476 ('/f a b', 'f(a, b)'),
475 ],
477 ],
476
478
477 # More complex multiline tests
478 ## escaped_multiline =
479 ## [()],
480 )
479 )
481
480
482 # 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
483 # 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
484 # '\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
485 # when fed the raw lines one at a time via push.
484 # when fed the raw lines one at a time via push.
486 syntax_ml = \
485 syntax_ml = \
487 dict(classic_prompt =
486 dict(classic_prompt =
488 [ [('>>> for i in range(10):','for i in range(10):'),
487 [ [('>>> for i in range(10):','for i in range(10):'),
489 ('... print i',' print i'),
488 ('... print i',' print i'),
490 ('... ', ''),
489 ('... ', ''),
491 ],
490 ],
492 ],
491 ],
493
492
494 ipy_prompt =
493 ipy_prompt =
495 [ [('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):'),
496 (' ....: print i',' print i'),
495 (' ....: print i',' print i'),
497 (' ....: ', ''),
496 (' ....: ', ''),
498 ],
497 ],
499 ],
498 ],
500 )
499 )
501
500
502
501
503 def test_assign_system():
502 def test_assign_system():
504 transform_checker(syntax['assign_system'], isp.transform_assign_system)
503 transform_checker(syntax['assign_system'], isp.transform_assign_system)
505
504
506
505
507 def test_assign_magic():
506 def test_assign_magic():
508 transform_checker(syntax['assign_magic'], isp.transform_assign_magic)
507 transform_checker(syntax['assign_magic'], isp.transform_assign_magic)
509
508
510
509
511 def test_classic_prompt():
510 def test_classic_prompt():
512 transform_checker(syntax['classic_prompt'], isp.transform_classic_prompt)
511 transform_checker(syntax['classic_prompt'], isp.transform_classic_prompt)
513 for example in syntax_ml['classic_prompt']:
512 for example in syntax_ml['classic_prompt']:
514 transform_checker(example, isp.transform_classic_prompt)
513 transform_checker(example, isp.transform_classic_prompt)
515
514
516
515
517 def test_ipy_prompt():
516 def test_ipy_prompt():
518 transform_checker(syntax['ipy_prompt'], isp.transform_ipy_prompt)
517 transform_checker(syntax['ipy_prompt'], isp.transform_ipy_prompt)
519 for example in syntax_ml['ipy_prompt']:
518 for example in syntax_ml['ipy_prompt']:
520 transform_checker(example, isp.transform_ipy_prompt)
519 transform_checker(example, isp.transform_ipy_prompt)
521
520
522
521
523 def test_escaped_noesc():
522 def test_escaped_noesc():
524 transform_checker(syntax['escaped_noesc'], isp.transform_escaped)
523 transform_checker(syntax['escaped_noesc'], isp.transform_escaped)
525
524
526
525
527 def test_escaped_shell():
526 def test_escaped_shell():
528 transform_checker(syntax['escaped_shell'], isp.transform_escaped)
527 transform_checker(syntax['escaped_shell'], isp.transform_escaped)
529
528
530
529
531 def test_escaped_help():
530 def test_escaped_help():
532 transform_checker(syntax['escaped_help'], isp.transform_escaped)
531 transform_checker(syntax['escaped_help'], isp.transform_escaped)
533
532
534
533
535 def test_escaped_magic():
534 def test_escaped_magic():
536 transform_checker(syntax['escaped_magic'], isp.transform_escaped)
535 transform_checker(syntax['escaped_magic'], isp.transform_escaped)
537
536
538
537
539 def test_escaped_quote():
538 def test_escaped_quote():
540 transform_checker(syntax['escaped_quote'], isp.transform_escaped)
539 transform_checker(syntax['escaped_quote'], isp.transform_escaped)
541
540
542
541
543 def test_escaped_quote2():
542 def test_escaped_quote2():
544 transform_checker(syntax['escaped_quote2'], isp.transform_escaped)
543 transform_checker(syntax['escaped_quote2'], isp.transform_escaped)
545
544
546
545
547 def test_escaped_paren():
546 def test_escaped_paren():
548 transform_checker(syntax['escaped_paren'], isp.transform_escaped)
547 transform_checker(syntax['escaped_paren'], isp.transform_escaped)
549
548
550
549
551 class IPythonInputTestCase(InputSplitterTestCase):
550 class IPythonInputTestCase(InputSplitterTestCase):
552 """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
553 re-run the same test battery on the new input splitter.
552 re-run the same test battery on the new input splitter.
554
553
555 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
556 were tested by individual functions, as part of the OO interface.
555 were tested by individual functions, as part of the OO interface.
557 """
556 """
557
558 def setUp(self):
558 def setUp(self):
559 self.isp = isp.IPythonInputSplitter()
559 self.isp = isp.IPythonInputSplitter(input_mode='append')
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):
588
589 # Deactivate tests that don't make sense for the block mode
590 test_push3 = test_split = lambda s: None
591
592 def setUp(self):
593 self.isp = isp.IPythonInputSplitter(input_mode='replace')
594
595 def test_syntax_multiline(self):
596 isp = self.isp
597 for example in syntax_ml.itervalues():
598 raw_parts = []
599 out_t_parts = []
600 for line_pairs in example:
601 for raw, out_t_part in line_pairs:
602 raw_parts.append(raw)
603 out_t_parts.append(out_t_part)
604
605 raw = '\n'.join(raw_parts)
606 out_t = '\n'.join(out_t_parts)
607
608 isp.push(raw)
609 out = isp.source_reset()
610 # Match ignoring trailing whitespace
611 self.assertEqual(out.rstrip(), out_t.rstrip())
612
613
587 #-----------------------------------------------------------------------------
614 #-----------------------------------------------------------------------------
588 # Main - use as a script
615 # Main - use as a script, mostly for developer experiments
589 #-----------------------------------------------------------------------------
616 #-----------------------------------------------------------------------------
590
617
591 if __name__ == '__main__':
618 if __name__ == '__main__':
592 # A simple demo for interactive experimentation. This code will not get
619 # A simple demo for interactive experimentation. This code will not get
593 # picked up by any test suite. Useful mostly for illustration and during
620 # picked up by any test suite.
594 # development.
595 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
621 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
596
622
597 # configure here the syntax to use, prompt and whether to autoindent
623 # configure here the syntax to use, prompt and whether to autoindent
598 #isp, start_prompt = InputSplitter(), '>>> '
624 #isp, start_prompt = InputSplitter(), '>>> '
599 isp, start_prompt = IPythonInputSplitter(), 'In> '
625 isp, start_prompt = IPythonInputSplitter(), 'In> '
600
626
601 autoindent = True
627 autoindent = True
602 #autoindent = False
628 #autoindent = False
603
629
604 try:
630 try:
605 while True:
631 while True:
606 prompt = start_prompt
632 prompt = start_prompt
607 while isp.push_accepts_more():
633 while isp.push_accepts_more():
608 indent = ' '*isp.indent_spaces
634 indent = ' '*isp.indent_spaces
609 if autoindent:
635 if autoindent:
610 line = indent + raw_input(prompt+indent)
636 line = indent + raw_input(prompt+indent)
611 else:
637 else:
612 line = raw_input(prompt)
638 line = raw_input(prompt)
613 isp.push(line)
639 isp.push(line)
614 prompt = '... '
640 prompt = '... '
615
641
616 # 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
617 # real interpreter would instead send it for execution somewhere.
643 # real interpreter would instead send it for execution somewhere.
618 #src = isp.source; raise EOFError # dbg
644 #src = isp.source; raise EOFError # dbg
619 src = isp.source_reset()
645 src = isp.source_reset()
620 print 'Input source was:\n', src
646 print 'Input source was:\n', src
621 except EOFError:
647 except EOFError:
622 print 'Bye'
648 print 'Bye'
@@ -1,343 +1,343 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3
3
4 TODO: Add support for retrieving the system default editor. Requires code
4 TODO: Add support for retrieving the system default editor. Requires code
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
6 Linux (use the xdg system).
6 Linux (use the xdg system).
7 """
7 """
8
8
9 # Standard library imports
9 # Standard library imports
10 from subprocess import Popen
10 from subprocess import Popen
11
11
12 # System library imports
12 # System library imports
13 from PyQt4 import QtCore, QtGui
13 from PyQt4 import QtCore, QtGui
14
14
15 # Local imports
15 # Local imports
16 from IPython.core.inputsplitter import IPythonInputSplitter
16 from IPython.core.inputsplitter import IPythonInputSplitter
17 from IPython.core.usage import default_banner
17 from IPython.core.usage import default_banner
18 from frontend_widget import FrontendWidget
18 from frontend_widget import FrontendWidget
19
19
20
20
21 class IPythonPromptBlock(object):
21 class IPythonPromptBlock(object):
22 """ An internal storage object for IPythonWidget.
22 """ An internal storage object for IPythonWidget.
23 """
23 """
24 def __init__(self, block, length, number):
24 def __init__(self, block, length, number):
25 self.block = block
25 self.block = block
26 self.length = length
26 self.length = length
27 self.number = number
27 self.number = number
28
28
29
29
30 class IPythonWidget(FrontendWidget):
30 class IPythonWidget(FrontendWidget):
31 """ A FrontendWidget for an IPython kernel.
31 """ A FrontendWidget for an IPython kernel.
32 """
32 """
33
33
34 # Signal emitted when an editor is needed for a file and the editor has been
34 # Signal emitted when an editor is needed for a file and the editor has been
35 # specified as 'custom'. See 'set_editor' for more information.
35 # specified as 'custom'. See 'set_editor' for more information.
36 custom_edit_requested = QtCore.pyqtSignal(object, object)
36 custom_edit_requested = QtCore.pyqtSignal(object, object)
37
37
38 # The default stylesheet: black text on a white background.
38 # The default stylesheet: black text on a white background.
39 default_stylesheet = """
39 default_stylesheet = """
40 .error { color: red; }
40 .error { color: red; }
41 .in-prompt { color: navy; }
41 .in-prompt { color: navy; }
42 .in-prompt-number { font-weight: bold; }
42 .in-prompt-number { font-weight: bold; }
43 .out-prompt { color: darkred; }
43 .out-prompt { color: darkred; }
44 .out-prompt-number { font-weight: bold; }
44 .out-prompt-number { font-weight: bold; }
45 """
45 """
46
46
47 # A dark stylesheet: white text on a black background.
47 # A dark stylesheet: white text on a black background.
48 dark_stylesheet = """
48 dark_stylesheet = """
49 QPlainTextEdit, QTextEdit { background-color: black; color: white }
49 QPlainTextEdit, QTextEdit { background-color: black; color: white }
50 QFrame { border: 1px solid grey; }
50 QFrame { border: 1px solid grey; }
51 .error { color: red; }
51 .error { color: red; }
52 .in-prompt { color: lime; }
52 .in-prompt { color: lime; }
53 .in-prompt-number { color: lime; font-weight: bold; }
53 .in-prompt-number { color: lime; font-weight: bold; }
54 .out-prompt { color: red; }
54 .out-prompt { color: red; }
55 .out-prompt-number { color: red; font-weight: bold; }
55 .out-prompt-number { color: red; font-weight: bold; }
56 """
56 """
57
57
58 # Default prompts.
58 # Default prompts.
59 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
59 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
60 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
60 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
61
61
62 # FrontendWidget protected class variables.
62 # FrontendWidget protected class variables.
63 #_input_splitter_class = IPythonInputSplitter
63 _input_splitter_class = IPythonInputSplitter
64
64
65 # IPythonWidget protected class variables.
65 # IPythonWidget protected class variables.
66 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
66 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
67 _payload_source_page = 'IPython.zmq.page.page'
67 _payload_source_page = 'IPython.zmq.page.page'
68
68
69 #---------------------------------------------------------------------------
69 #---------------------------------------------------------------------------
70 # 'object' interface
70 # 'object' interface
71 #---------------------------------------------------------------------------
71 #---------------------------------------------------------------------------
72
72
73 def __init__(self, *args, **kw):
73 def __init__(self, *args, **kw):
74 super(IPythonWidget, self).__init__(*args, **kw)
74 super(IPythonWidget, self).__init__(*args, **kw)
75
75
76 # IPythonWidget protected variables.
76 # IPythonWidget protected variables.
77 self._previous_prompt_obj = None
77 self._previous_prompt_obj = None
78
78
79 # Set a default editor and stylesheet.
79 # Set a default editor and stylesheet.
80 self.set_editor('default')
80 self.set_editor('default')
81 self.reset_styling()
81 self.reset_styling()
82
82
83 #---------------------------------------------------------------------------
83 #---------------------------------------------------------------------------
84 # 'BaseFrontendMixin' abstract interface
84 # 'BaseFrontendMixin' abstract interface
85 #---------------------------------------------------------------------------
85 #---------------------------------------------------------------------------
86
86
87 def _handle_history_reply(self, msg):
87 def _handle_history_reply(self, msg):
88 """ Implemented to handle history replies, which are only supported by
88 """ Implemented to handle history replies, which are only supported by
89 the IPython kernel.
89 the IPython kernel.
90 """
90 """
91 history_dict = msg['content']['history']
91 history_dict = msg['content']['history']
92 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
92 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
93 self._set_history(items)
93 self._set_history(items)
94
94
95 def _handle_prompt_reply(self, msg):
95 def _handle_prompt_reply(self, msg):
96 """ Implemented to handle prompt number replies, which are only
96 """ Implemented to handle prompt number replies, which are only
97 supported by the IPython kernel.
97 supported by the IPython kernel.
98 """
98 """
99 content = msg['content']
99 content = msg['content']
100 self._show_interpreter_prompt(content['prompt_number'],
100 self._show_interpreter_prompt(content['prompt_number'],
101 content['input_sep'])
101 content['input_sep'])
102
102
103 def _handle_pyout(self, msg):
103 def _handle_pyout(self, msg):
104 """ Reimplemented for IPython-style "display hook".
104 """ Reimplemented for IPython-style "display hook".
105 """
105 """
106 if not self._hidden and self._is_from_this_session(msg):
106 if not self._hidden and self._is_from_this_session(msg):
107 content = msg['content']
107 content = msg['content']
108 prompt_number = content['prompt_number']
108 prompt_number = content['prompt_number']
109 self._append_plain_text(content['output_sep'])
109 self._append_plain_text(content['output_sep'])
110 self._append_html(self._make_out_prompt(prompt_number))
110 self._append_html(self._make_out_prompt(prompt_number))
111 self._append_plain_text(content['data'] + '\n' +
111 self._append_plain_text(content['data'] + '\n' +
112 content['output_sep2'])
112 content['output_sep2'])
113
113
114 def _started_channels(self):
114 def _started_channels(self):
115 """ Reimplemented to make a history request.
115 """ Reimplemented to make a history request.
116 """
116 """
117 super(IPythonWidget, self)._started_channels()
117 super(IPythonWidget, self)._started_channels()
118 # FIXME: Disabled until history requests are properly implemented.
118 # FIXME: Disabled until history requests are properly implemented.
119 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
119 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
120
120
121 #---------------------------------------------------------------------------
121 #---------------------------------------------------------------------------
122 # 'FrontendWidget' interface
122 # 'FrontendWidget' interface
123 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
124
124
125 def execute_file(self, path, hidden=False):
125 def execute_file(self, path, hidden=False):
126 """ Reimplemented to use the 'run' magic.
126 """ Reimplemented to use the 'run' magic.
127 """
127 """
128 self.execute('%%run %s' % path, hidden=hidden)
128 self.execute('%%run %s' % path, hidden=hidden)
129
129
130 #---------------------------------------------------------------------------
130 #---------------------------------------------------------------------------
131 # 'FrontendWidget' protected interface
131 # 'FrontendWidget' protected interface
132 #---------------------------------------------------------------------------
132 #---------------------------------------------------------------------------
133
133
134 def _get_banner(self):
134 def _get_banner(self):
135 """ Reimplemented to return IPython's default banner.
135 """ Reimplemented to return IPython's default banner.
136 """
136 """
137 return default_banner + '\n'
137 return default_banner + '\n'
138
138
139 def _process_execute_error(self, msg):
139 def _process_execute_error(self, msg):
140 """ Reimplemented for IPython-style traceback formatting.
140 """ Reimplemented for IPython-style traceback formatting.
141 """
141 """
142 content = msg['content']
142 content = msg['content']
143 traceback = '\n'.join(content['traceback']) + '\n'
143 traceback = '\n'.join(content['traceback']) + '\n'
144 if False:
144 if False:
145 # FIXME: For now, tracebacks come as plain text, so we can't use
145 # FIXME: For now, tracebacks come as plain text, so we can't use
146 # the html renderer yet. Once we refactor ultratb to produce
146 # the html renderer yet. Once we refactor ultratb to produce
147 # properly styled tracebacks, this branch should be the default
147 # properly styled tracebacks, this branch should be the default
148 traceback = traceback.replace(' ', '&nbsp;')
148 traceback = traceback.replace(' ', '&nbsp;')
149 traceback = traceback.replace('\n', '<br/>')
149 traceback = traceback.replace('\n', '<br/>')
150
150
151 ename = content['ename']
151 ename = content['ename']
152 ename_styled = '<span class="error">%s</span>' % ename
152 ename_styled = '<span class="error">%s</span>' % ename
153 traceback = traceback.replace(ename, ename_styled)
153 traceback = traceback.replace(ename, ename_styled)
154
154
155 self._append_html(traceback)
155 self._append_html(traceback)
156 else:
156 else:
157 # This is the fallback for now, using plain text with ansi escapes
157 # This is the fallback for now, using plain text with ansi escapes
158 self._append_plain_text(traceback)
158 self._append_plain_text(traceback)
159
159
160 def _process_execute_payload(self, item):
160 def _process_execute_payload(self, item):
161 """ Reimplemented to handle %edit and paging payloads.
161 """ Reimplemented to handle %edit and paging payloads.
162 """
162 """
163 if item['source'] == self._payload_source_edit:
163 if item['source'] == self._payload_source_edit:
164 self._edit(item['filename'], item['line_number'])
164 self._edit(item['filename'], item['line_number'])
165 return True
165 return True
166 elif item['source'] == self._payload_source_page:
166 elif item['source'] == self._payload_source_page:
167 self._page(item['data'])
167 self._page(item['data'])
168 return True
168 return True
169 else:
169 else:
170 return False
170 return False
171
171
172 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
172 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
173 """ Reimplemented for IPython-style prompts.
173 """ Reimplemented for IPython-style prompts.
174 """
174 """
175 # If a number was not specified, make a prompt number request.
175 # If a number was not specified, make a prompt number request.
176 if number is None:
176 if number is None:
177 self.kernel_manager.xreq_channel.prompt()
177 self.kernel_manager.xreq_channel.prompt()
178 return
178 return
179
179
180 # Show a new prompt and save information about it so that it can be
180 # Show a new prompt and save information about it so that it can be
181 # updated later if the prompt number turns out to be wrong.
181 # updated later if the prompt number turns out to be wrong.
182 self._append_plain_text(input_sep)
182 self._append_plain_text(input_sep)
183 self._show_prompt(self._make_in_prompt(number), html=True)
183 self._show_prompt(self._make_in_prompt(number), html=True)
184 block = self._control.document().lastBlock()
184 block = self._control.document().lastBlock()
185 length = len(self._prompt)
185 length = len(self._prompt)
186 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
186 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
187
187
188 # Update continuation prompt to reflect (possibly) new prompt length.
188 # Update continuation prompt to reflect (possibly) new prompt length.
189 self._set_continuation_prompt(
189 self._set_continuation_prompt(
190 self._make_continuation_prompt(self._prompt), html=True)
190 self._make_continuation_prompt(self._prompt), html=True)
191
191
192 def _show_interpreter_prompt_for_reply(self, msg):
192 def _show_interpreter_prompt_for_reply(self, msg):
193 """ Reimplemented for IPython-style prompts.
193 """ Reimplemented for IPython-style prompts.
194 """
194 """
195 # Update the old prompt number if necessary.
195 # Update the old prompt number if necessary.
196 content = msg['content']
196 content = msg['content']
197 previous_prompt_number = content['prompt_number']
197 previous_prompt_number = content['prompt_number']
198 if self._previous_prompt_obj and \
198 if self._previous_prompt_obj and \
199 self._previous_prompt_obj.number != previous_prompt_number:
199 self._previous_prompt_obj.number != previous_prompt_number:
200 block = self._previous_prompt_obj.block
200 block = self._previous_prompt_obj.block
201
201
202 # Make sure the prompt block has not been erased.
202 # Make sure the prompt block has not been erased.
203 if block.isValid() and not block.text().isEmpty():
203 if block.isValid() and not block.text().isEmpty():
204
204
205 # Remove the old prompt and insert a new prompt.
205 # Remove the old prompt and insert a new prompt.
206 cursor = QtGui.QTextCursor(block)
206 cursor = QtGui.QTextCursor(block)
207 cursor.movePosition(QtGui.QTextCursor.Right,
207 cursor.movePosition(QtGui.QTextCursor.Right,
208 QtGui.QTextCursor.KeepAnchor,
208 QtGui.QTextCursor.KeepAnchor,
209 self._previous_prompt_obj.length)
209 self._previous_prompt_obj.length)
210 prompt = self._make_in_prompt(previous_prompt_number)
210 prompt = self._make_in_prompt(previous_prompt_number)
211 self._prompt = self._insert_html_fetching_plain_text(
211 self._prompt = self._insert_html_fetching_plain_text(
212 cursor, prompt)
212 cursor, prompt)
213
213
214 # When the HTML is inserted, Qt blows away the syntax
214 # When the HTML is inserted, Qt blows away the syntax
215 # highlighting for the line, so we need to rehighlight it.
215 # highlighting for the line, so we need to rehighlight it.
216 self._highlighter.rehighlightBlock(cursor.block())
216 self._highlighter.rehighlightBlock(cursor.block())
217
217
218 self._previous_prompt_obj = None
218 self._previous_prompt_obj = None
219
219
220 # Show a new prompt with the kernel's estimated prompt number.
220 # Show a new prompt with the kernel's estimated prompt number.
221 next_prompt = content['next_prompt']
221 next_prompt = content['next_prompt']
222 self._show_interpreter_prompt(next_prompt['prompt_number'],
222 self._show_interpreter_prompt(next_prompt['prompt_number'],
223 next_prompt['input_sep'])
223 next_prompt['input_sep'])
224
224
225 #---------------------------------------------------------------------------
225 #---------------------------------------------------------------------------
226 # 'IPythonWidget' interface
226 # 'IPythonWidget' interface
227 #---------------------------------------------------------------------------
227 #---------------------------------------------------------------------------
228
228
229 def reset_styling(self):
229 def reset_styling(self):
230 """ Restores the default IPythonWidget styling.
230 """ Restores the default IPythonWidget styling.
231 """
231 """
232 self.set_styling(self.default_stylesheet, syntax_style='default')
232 self.set_styling(self.default_stylesheet, syntax_style='default')
233 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
233 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
234
234
235 def set_editor(self, editor, line_editor=None):
235 def set_editor(self, editor, line_editor=None):
236 """ Sets the editor to use with the %edit magic.
236 """ Sets the editor to use with the %edit magic.
237
237
238 Parameters:
238 Parameters:
239 -----------
239 -----------
240 editor : str
240 editor : str
241 A command for invoking a system text editor. If the string contains
241 A command for invoking a system text editor. If the string contains
242 a {filename} format specifier, it will be used. Otherwise, the
242 a {filename} format specifier, it will be used. Otherwise, the
243 filename will be appended to the end the command.
243 filename will be appended to the end the command.
244
244
245 This parameter also takes a special value:
245 This parameter also takes a special value:
246 'custom' : Emit a 'custom_edit_requested(str, int)' signal
246 'custom' : Emit a 'custom_edit_requested(str, int)' signal
247 instead of opening an editor.
247 instead of opening an editor.
248
248
249 line_editor : str, optional
249 line_editor : str, optional
250 The editor command to use when a specific line number is
250 The editor command to use when a specific line number is
251 requested. The string should contain two format specifiers: {line}
251 requested. The string should contain two format specifiers: {line}
252 and {filename}. If this parameter is not specified, the line number
252 and {filename}. If this parameter is not specified, the line number
253 option to the %edit magic will be ignored.
253 option to the %edit magic will be ignored.
254 """
254 """
255 self._editor = editor
255 self._editor = editor
256 self._editor_line = line_editor
256 self._editor_line = line_editor
257
257
258 def set_styling(self, stylesheet, syntax_style=None):
258 def set_styling(self, stylesheet, syntax_style=None):
259 """ Sets the IPythonWidget styling.
259 """ Sets the IPythonWidget styling.
260
260
261 Parameters:
261 Parameters:
262 -----------
262 -----------
263 stylesheet : str
263 stylesheet : str
264 A CSS stylesheet. The stylesheet can contain classes for:
264 A CSS stylesheet. The stylesheet can contain classes for:
265 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
265 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
266 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
266 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
267 3. IPython: .error, .in-prompt, .out-prompt, etc.
267 3. IPython: .error, .in-prompt, .out-prompt, etc.
268
268
269 syntax_style : str or None [default None]
269 syntax_style : str or None [default None]
270 If specified, use the Pygments style with given name. Otherwise,
270 If specified, use the Pygments style with given name. Otherwise,
271 the stylesheet is queried for Pygments style information.
271 the stylesheet is queried for Pygments style information.
272 """
272 """
273 self.setStyleSheet(stylesheet)
273 self.setStyleSheet(stylesheet)
274 self._control.document().setDefaultStyleSheet(stylesheet)
274 self._control.document().setDefaultStyleSheet(stylesheet)
275 if self._page_control:
275 if self._page_control:
276 self._page_control.document().setDefaultStyleSheet(stylesheet)
276 self._page_control.document().setDefaultStyleSheet(stylesheet)
277
277
278 if syntax_style is None:
278 if syntax_style is None:
279 self._highlighter.set_style_sheet(stylesheet)
279 self._highlighter.set_style_sheet(stylesheet)
280 else:
280 else:
281 self._highlighter.set_style(syntax_style)
281 self._highlighter.set_style(syntax_style)
282
282
283 #---------------------------------------------------------------------------
283 #---------------------------------------------------------------------------
284 # 'IPythonWidget' protected interface
284 # 'IPythonWidget' protected interface
285 #---------------------------------------------------------------------------
285 #---------------------------------------------------------------------------
286
286
287 def _edit(self, filename, line=None):
287 def _edit(self, filename, line=None):
288 """ Opens a Python script for editing.
288 """ Opens a Python script for editing.
289
289
290 Parameters:
290 Parameters:
291 -----------
291 -----------
292 filename : str
292 filename : str
293 A path to a local system file.
293 A path to a local system file.
294
294
295 line : int, optional
295 line : int, optional
296 A line of interest in the file.
296 A line of interest in the file.
297 """
297 """
298 if self._editor == 'custom':
298 if self._editor == 'custom':
299 self.custom_edit_requested.emit(filename, line)
299 self.custom_edit_requested.emit(filename, line)
300 elif self._editor == 'default':
300 elif self._editor == 'default':
301 self._append_plain_text('No default editor available.\n')
301 self._append_plain_text('No default editor available.\n')
302 else:
302 else:
303 try:
303 try:
304 filename = '"%s"' % filename
304 filename = '"%s"' % filename
305 if line and self._editor_line:
305 if line and self._editor_line:
306 command = self._editor_line.format(filename=filename,
306 command = self._editor_line.format(filename=filename,
307 line=line)
307 line=line)
308 else:
308 else:
309 try:
309 try:
310 command = self._editor.format()
310 command = self._editor.format()
311 except KeyError:
311 except KeyError:
312 command = self._editor.format(filename=filename)
312 command = self._editor.format(filename=filename)
313 else:
313 else:
314 command += ' ' + filename
314 command += ' ' + filename
315 except KeyError:
315 except KeyError:
316 self._append_plain_text('Invalid editor command.\n')
316 self._append_plain_text('Invalid editor command.\n')
317 else:
317 else:
318 try:
318 try:
319 Popen(command, shell=True)
319 Popen(command, shell=True)
320 except OSError:
320 except OSError:
321 msg = 'Opening editor with command "%s" failed.\n'
321 msg = 'Opening editor with command "%s" failed.\n'
322 self._append_plain_text(msg % command)
322 self._append_plain_text(msg % command)
323
323
324 def _make_in_prompt(self, number):
324 def _make_in_prompt(self, number):
325 """ Given a prompt number, returns an HTML In prompt.
325 """ Given a prompt number, returns an HTML In prompt.
326 """
326 """
327 body = self.in_prompt % number
327 body = self.in_prompt % number
328 return '<span class="in-prompt">%s</span>' % body
328 return '<span class="in-prompt">%s</span>' % body
329
329
330 def _make_continuation_prompt(self, prompt):
330 def _make_continuation_prompt(self, prompt):
331 """ Given a plain text version of an In prompt, returns an HTML
331 """ Given a plain text version of an In prompt, returns an HTML
332 continuation prompt.
332 continuation prompt.
333 """
333 """
334 end_chars = '...: '
334 end_chars = '...: '
335 space_count = len(prompt.lstrip('\n')) - len(end_chars)
335 space_count = len(prompt.lstrip('\n')) - len(end_chars)
336 body = '&nbsp;' * space_count + end_chars
336 body = '&nbsp;' * space_count + end_chars
337 return '<span class="in-prompt">%s</span>' % body
337 return '<span class="in-prompt">%s</span>' % body
338
338
339 def _make_out_prompt(self, number):
339 def _make_out_prompt(self, number):
340 """ Given a prompt number, returns an HTML Out prompt.
340 """ Given a prompt number, returns an HTML Out prompt.
341 """
341 """
342 body = self.out_prompt % number
342 body = self.out_prompt % number
343 return '<span class="out-prompt">%s</span>' % body
343 return '<span class="out-prompt">%s</span>' % body
General Comments 0
You need to be logged in to leave comments. Login now