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