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