##// END OF EJS Templates
Implemented %loadpy magic for loading .py scripts into Qt console.
Brian Granger -
Show More
@@ -1,459 +1,473 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 TODO: Add support for retrieving the system default editor. Requires code
5 5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
6 6 Linux (use the xdg system).
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Imports
11 11 #-----------------------------------------------------------------------------
12 12
13 13 # Standard library imports
14 14 from collections import namedtuple
15 15 import re
16 16 from subprocess import Popen
17 from textwrap import dedent
17 18
18 19 # System library imports
19 20 from PyQt4 import QtCore, QtGui
20 21
21 22 # Local imports
22 23 from IPython.core.inputsplitter import IPythonInputSplitter, \
23 24 transform_ipy_prompt
24 25 from IPython.core.usage import default_gui_banner
25 26 from IPython.utils.traitlets import Bool, Str
26 27 from frontend_widget import FrontendWidget
27 28
28 29 #-----------------------------------------------------------------------------
29 30 # Constants
30 31 #-----------------------------------------------------------------------------
31 32
32 33 # The default light style sheet: black text on a white background.
33 34 default_light_style_sheet = '''
34 35 .error { color: red; }
35 36 .in-prompt { color: navy; }
36 37 .in-prompt-number { font-weight: bold; }
37 38 .out-prompt { color: darkred; }
38 39 .out-prompt-number { font-weight: bold; }
39 40 '''
40 41 default_light_syntax_style = 'default'
41 42
42 43 # The default dark style sheet: white text on a black background.
43 44 default_dark_style_sheet = '''
44 45 QPlainTextEdit, QTextEdit { background-color: black; color: white }
45 46 QFrame { border: 1px solid grey; }
46 47 .error { color: red; }
47 48 .in-prompt { color: lime; }
48 49 .in-prompt-number { color: lime; font-weight: bold; }
49 50 .out-prompt { color: red; }
50 51 .out-prompt-number { color: red; font-weight: bold; }
51 52 '''
52 53 default_dark_syntax_style = 'monokai'
53 54
54 55 # Default strings to build and display input and output prompts (and separators
55 56 # in between)
56 57 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
57 58 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
58 59 default_input_sep = '\n'
59 60 default_output_sep = ''
60 61 default_output_sep2 = ''
61 62
62 63 #-----------------------------------------------------------------------------
63 64 # IPythonWidget class
64 65 #-----------------------------------------------------------------------------
65 66
66 67 class IPythonWidget(FrontendWidget):
67 68 """ A FrontendWidget for an IPython kernel.
68 69 """
69 70
70 71 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
71 72 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
72 73 # settings.
73 74 custom_edit = Bool(False)
74 75 custom_edit_requested = QtCore.pyqtSignal(object, object)
75 76
76 77 # A command for invoking a system text editor. If the string contains a
77 78 # {filename} format specifier, it will be used. Otherwise, the filename will
78 79 # be appended to the end the command.
79 80 editor = Str('default', config=True)
80 81
81 82 # The editor command to use when a specific line number is requested. The
82 83 # string should contain two format specifiers: {line} and {filename}. If
83 84 # this parameter is not specified, the line number option to the %edit magic
84 85 # will be ignored.
85 86 editor_line = Str(config=True)
86 87
87 88 # A CSS stylesheet. The stylesheet can contain classes for:
88 89 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
89 90 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
90 91 # 3. IPython: .error, .in-prompt, .out-prompt, etc
91 92 style_sheet = Str(config=True)
92 93
93 94 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
94 95 # the style sheet is queried for Pygments style information.
95 96 syntax_style = Str(config=True)
96 97
97 98 # Prompts.
98 99 in_prompt = Str(default_in_prompt, config=True)
99 100 out_prompt = Str(default_out_prompt, config=True)
100 101 input_sep = Str(default_input_sep, config=True)
101 102 output_sep = Str(default_output_sep, config=True)
102 103 output_sep2 = Str(default_output_sep2, config=True)
103 104
104 105 # FrontendWidget protected class variables.
105 106 _input_splitter_class = IPythonInputSplitter
106 107
107 108 # IPythonWidget protected class variables.
108 109 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
109 110 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
110 111 _payload_source_exit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit'
111 112 _payload_source_page = 'IPython.zmq.page.page'
113 _payload_source_loadpy = 'IPython.zmq.zmqshell.ZMQInteractiveShell.magic_loadpy'
112 114
113 115 #---------------------------------------------------------------------------
114 116 # 'object' interface
115 117 #---------------------------------------------------------------------------
116 118
117 119 def __init__(self, *args, **kw):
118 120 super(IPythonWidget, self).__init__(*args, **kw)
119 121
120 122 # IPythonWidget protected variables.
121 123 self._payload_handlers = {
122 124 self._payload_source_edit : self._handle_payload_edit,
123 125 self._payload_source_exit : self._handle_payload_exit,
124 self._payload_source_page : self._handle_payload_page }
126 self._payload_source_page : self._handle_payload_page,
127 self._payload_source_loadpy : self._handle_payload_loadpy }
125 128 self._previous_prompt_obj = None
129 self._code_to_load = None
126 130
127 131 # Initialize widget styling.
128 132 if self.style_sheet:
129 133 self._style_sheet_changed()
130 134 self._syntax_style_changed()
131 135 else:
132 136 self.set_default_style()
133 137
134 138 #---------------------------------------------------------------------------
135 139 # 'BaseFrontendMixin' abstract interface
136 140 #---------------------------------------------------------------------------
137 141
138 142 def _handle_complete_reply(self, rep):
139 143 """ Reimplemented to support IPython's improved completion machinery.
140 144 """
141 145 cursor = self._get_cursor()
142 146 info = self._request_info.get('complete')
143 147 if info and info.id == rep['parent_header']['msg_id'] and \
144 148 info.pos == cursor.position():
145 149 matches = rep['content']['matches']
146 150 text = rep['content']['matched_text']
147 151 offset = len(text)
148 152
149 153 # Clean up matches with period and path separators if the matched
150 154 # text has not been transformed. This is done by truncating all
151 155 # but the last component and then suitably decreasing the offset
152 156 # between the current cursor position and the start of completion.
153 157 if len(matches) > 1 and matches[0][:offset] == text:
154 158 parts = re.split(r'[./\\]', text)
155 159 sep_count = len(parts) - 1
156 160 if sep_count:
157 161 chop_length = sum(map(len, parts[:sep_count])) + sep_count
158 162 matches = [ match[chop_length:] for match in matches ]
159 163 offset -= chop_length
160 164
161 165 # Move the cursor to the start of the match and complete.
162 166 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
163 167 self._complete_with_items(cursor, matches)
164 168
165 169 def _handle_execute_reply(self, msg):
166 170 """ Reimplemented to support prompt requests.
167 171 """
168 172 info = self._request_info.get('execute')
169 173 if info and info.id == msg['parent_header']['msg_id']:
170 174 if info.kind == 'prompt':
171 175 number = msg['content']['execution_count'] + 1
172 176 self._show_interpreter_prompt(number)
173 177 else:
174 178 super(IPythonWidget, self)._handle_execute_reply(msg)
175 179
176 180 def _handle_history_reply(self, msg):
177 181 """ Implemented to handle history replies, which are only supported by
178 182 the IPython kernel.
179 183 """
180 184 history_dict = msg['content']['history']
181 185 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
182 186 self._set_history(items)
183 187
184 188 def _handle_pyout(self, msg):
185 189 """ Reimplemented for IPython-style "display hook".
186 190 """
187 191 if not self._hidden and self._is_from_this_session(msg):
188 192 content = msg['content']
189 193 prompt_number = content['execution_count']
190 194 self._append_plain_text(self.output_sep)
191 195 self._append_html(self._make_out_prompt(prompt_number))
192 196 self._append_plain_text(content['data']+self.output_sep2)
193 197
194 198 def _started_channels(self):
195 199 """ Reimplemented to make a history request.
196 200 """
197 201 super(IPythonWidget, self)._started_channels()
198 202 # FIXME: Disabled until history requests are properly implemented.
199 203 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
200 204
201 205 #---------------------------------------------------------------------------
202 206 # 'ConsoleWidget' public interface
203 207 #---------------------------------------------------------------------------
204 208
205 209 def copy(self):
206 210 """ Copy the currently selected text to the clipboard, removing prompts
207 211 if possible.
208 212 """
209 213 text = unicode(self._control.textCursor().selection().toPlainText())
210 214 if text:
211 215 lines = map(transform_ipy_prompt, text.splitlines())
212 216 text = '\n'.join(lines)
213 217 QtGui.QApplication.clipboard().setText(text)
214 218
215 219 #---------------------------------------------------------------------------
216 220 # 'FrontendWidget' public interface
217 221 #---------------------------------------------------------------------------
218 222
219 223 def execute_file(self, path, hidden=False):
220 224 """ Reimplemented to use the 'run' magic.
221 225 """
222 226 self.execute('%%run %s' % path, hidden=hidden)
223 227
224 228 #---------------------------------------------------------------------------
225 229 # 'FrontendWidget' protected interface
226 230 #---------------------------------------------------------------------------
227 231
228 232 def _complete(self):
229 233 """ Reimplemented to support IPython's improved completion machinery.
230 234 """
231 235 # We let the kernel split the input line, so we *always* send an empty
232 236 # text field. Readline-based frontends do get a real text field which
233 237 # they can use.
234 238 text = ''
235 239
236 240 # Send the completion request to the kernel
237 241 msg_id = self.kernel_manager.xreq_channel.complete(
238 242 text, # text
239 243 self._get_input_buffer_cursor_line(), # line
240 244 self._get_input_buffer_cursor_column(), # cursor_pos
241 245 self.input_buffer) # block
242 246 pos = self._get_cursor().position()
243 247 info = self._CompletionRequest(msg_id, pos)
244 248 self._request_info['complete'] = info
245 249
246 250 def _get_banner(self):
247 251 """ Reimplemented to return IPython's default banner.
248 252 """
249 253 return default_gui_banner
250 254
251 255 def _process_execute_error(self, msg):
252 256 """ Reimplemented for IPython-style traceback formatting.
253 257 """
254 258 content = msg['content']
255 259 traceback = '\n'.join(content['traceback']) + '\n'
256 260 if False:
257 261 # FIXME: For now, tracebacks come as plain text, so we can't use
258 262 # the html renderer yet. Once we refactor ultratb to produce
259 263 # properly styled tracebacks, this branch should be the default
260 264 traceback = traceback.replace(' ', '&nbsp;')
261 265 traceback = traceback.replace('\n', '<br/>')
262 266
263 267 ename = content['ename']
264 268 ename_styled = '<span class="error">%s</span>' % ename
265 269 traceback = traceback.replace(ename, ename_styled)
266 270
267 271 self._append_html(traceback)
268 272 else:
269 273 # This is the fallback for now, using plain text with ansi escapes
270 274 self._append_plain_text(traceback)
271 275
272 276 def _process_execute_payload(self, item):
273 277 """ Reimplemented to dispatch payloads to handler methods.
274 278 """
275 279 handler = self._payload_handlers.get(item['source'])
276 280 if handler is None:
277 281 # We have no handler for this type of payload, simply ignore it
278 282 return False
279 283 else:
280 284 handler(item)
281 285 return True
282 286
283 287 def _show_interpreter_prompt(self, number=None):
284 288 """ Reimplemented for IPython-style prompts.
285 289 """
286 290 # If a number was not specified, make a prompt number request.
287 291 if number is None:
288 292 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
289 293 info = self._ExecutionRequest(msg_id, 'prompt')
290 294 self._request_info['execute'] = info
291 295 return
292 296
293 297 # Show a new prompt and save information about it so that it can be
294 298 # updated later if the prompt number turns out to be wrong.
295 299 self._prompt_sep = self.input_sep
296 300 self._show_prompt(self._make_in_prompt(number), html=True)
297 301 block = self._control.document().lastBlock()
298 302 length = len(self._prompt)
299 303 self._previous_prompt_obj = self._PromptBlock(block, length, number)
300 304
301 305 # Update continuation prompt to reflect (possibly) new prompt length.
302 306 self._set_continuation_prompt(
303 307 self._make_continuation_prompt(self._prompt), html=True)
304 308
309 if self._code_to_load is not None:
310 text = unicode(self._code_to_load).rstrip()
311 self._insert_plain_text_into_buffer(dedent(text))
312 self._code_to_load = None
313
305 314 def _show_interpreter_prompt_for_reply(self, msg):
306 315 """ Reimplemented for IPython-style prompts.
307 316 """
308 317 # Update the old prompt number if necessary.
309 318 content = msg['content']
310 319 previous_prompt_number = content['execution_count']
311 320 if self._previous_prompt_obj and \
312 321 self._previous_prompt_obj.number != previous_prompt_number:
313 322 block = self._previous_prompt_obj.block
314 323
315 324 # Make sure the prompt block has not been erased.
316 325 if block.isValid() and not block.text().isEmpty():
317 326
318 327 # Remove the old prompt and insert a new prompt.
319 328 cursor = QtGui.QTextCursor(block)
320 329 cursor.movePosition(QtGui.QTextCursor.Right,
321 330 QtGui.QTextCursor.KeepAnchor,
322 331 self._previous_prompt_obj.length)
323 332 prompt = self._make_in_prompt(previous_prompt_number)
324 333 self._prompt = self._insert_html_fetching_plain_text(
325 334 cursor, prompt)
326 335
327 336 # When the HTML is inserted, Qt blows away the syntax
328 337 # highlighting for the line, so we need to rehighlight it.
329 338 self._highlighter.rehighlightBlock(cursor.block())
330 339
331 340 self._previous_prompt_obj = None
332 341
333 342 # Show a new prompt with the kernel's estimated prompt number.
334 343 self._show_interpreter_prompt(previous_prompt_number+1)
335 344
336 345 #---------------------------------------------------------------------------
337 346 # 'IPythonWidget' interface
338 347 #---------------------------------------------------------------------------
339 348
340 349 def set_default_style(self, lightbg=True):
341 350 """ Sets the widget style to the class defaults.
342 351
343 352 Parameters:
344 353 -----------
345 354 lightbg : bool, optional (default True)
346 355 Whether to use the default IPython light background or dark
347 356 background style.
348 357 """
349 358 if lightbg:
350 359 self.style_sheet = default_light_style_sheet
351 360 self.syntax_style = default_light_syntax_style
352 361 else:
353 362 self.style_sheet = default_dark_style_sheet
354 363 self.syntax_style = default_dark_syntax_style
355 364
356 365 #---------------------------------------------------------------------------
357 366 # 'IPythonWidget' protected interface
358 367 #---------------------------------------------------------------------------
359 368
360 369 def _edit(self, filename, line=None):
361 370 """ Opens a Python script for editing.
362 371
363 372 Parameters:
364 373 -----------
365 374 filename : str
366 375 A path to a local system file.
367 376
368 377 line : int, optional
369 378 A line of interest in the file.
370 379 """
371 380 if self.custom_edit:
372 381 self.custom_edit_requested.emit(filename, line)
373 382 elif self.editor == 'default':
374 383 self._append_plain_text('No default editor available.\n')
375 384 else:
376 385 try:
377 386 filename = '"%s"' % filename
378 387 if line and self.editor_line:
379 388 command = self.editor_line.format(filename=filename,
380 389 line=line)
381 390 else:
382 391 try:
383 392 command = self.editor.format()
384 393 except KeyError:
385 394 command = self.editor.format(filename=filename)
386 395 else:
387 396 command += ' ' + filename
388 397 except KeyError:
389 398 self._append_plain_text('Invalid editor command.\n')
390 399 else:
391 400 try:
392 401 Popen(command, shell=True)
393 402 except OSError:
394 403 msg = 'Opening editor with command "%s" failed.\n'
395 404 self._append_plain_text(msg % command)
396 405
397 406 def _make_in_prompt(self, number):
398 407 """ Given a prompt number, returns an HTML In prompt.
399 408 """
400 409 body = self.in_prompt % number
401 410 return '<span class="in-prompt">%s</span>' % body
402 411
403 412 def _make_continuation_prompt(self, prompt):
404 413 """ Given a plain text version of an In prompt, returns an HTML
405 414 continuation prompt.
406 415 """
407 416 end_chars = '...: '
408 417 space_count = len(prompt.lstrip('\n')) - len(end_chars)
409 418 body = '&nbsp;' * space_count + end_chars
410 419 return '<span class="in-prompt">%s</span>' % body
411 420
412 421 def _make_out_prompt(self, number):
413 422 """ Given a prompt number, returns an HTML Out prompt.
414 423 """
415 424 body = self.out_prompt % number
416 425 return '<span class="out-prompt">%s</span>' % body
417 426
418 427 #------ Payload handlers --------------------------------------------------
419 428
420 429 # Payload handlers with a generic interface: each takes the opaque payload
421 430 # dict, unpacks it and calls the underlying functions with the necessary
422 431 # arguments.
423 432
424 433 def _handle_payload_edit(self, item):
425 434 self._edit(item['filename'], item['line_number'])
426 435
427 436 def _handle_payload_exit(self, item):
428 437 self.exit_requested.emit()
429 438
430 439 def _handle_payload_page(self, item):
431 440 # Since the plain text widget supports only a very small subset of HTML
432 441 # and we have no control over the HTML source, we only page HTML
433 442 # payloads in the rich text widget.
434 443 if item['html'] and self.kind == 'rich':
435 444 self._page(item['html'], html=True)
436 445 else:
437 446 self._page(item['text'], html=False)
438 447
448 def _handle_payload_loadpy(self, item):
449 # Simple save the text of the .py file for later. The text is written
450 # to the buffer when _prompt_started_hook is called.
451 self._code_to_load = item['text']
452
439 453 #------ Trait change handlers ---------------------------------------------
440 454
441 455 def _style_sheet_changed(self):
442 456 """ Set the style sheets of the underlying widgets.
443 457 """
444 458 self.setStyleSheet(self.style_sheet)
445 459 self._control.document().setDefaultStyleSheet(self.style_sheet)
446 460 if self._page_control:
447 461 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
448 462
449 463 bg_color = self._control.palette().background().color()
450 464 self._ansi_processor.set_background_color(bg_color)
451 465
452 466 def _syntax_style_changed(self):
453 467 """ Set the style for the syntax highlighter.
454 468 """
455 469 if self.syntax_style:
456 470 self._highlighter.set_style(self.syntax_style)
457 471 else:
458 472 self._highlighter.set_style_sheet(self.style_sheet)
459 473
@@ -1,546 +1,567 b''
1 1 """A ZMQ-based subclass of InteractiveShell.
2 2
3 3 This code is meant to ease the refactoring of the base InteractiveShell into
4 4 something with a cleaner architecture for 2-process use, without actually
5 5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 6 we subclass and override what we want to fix. Once this is working well, we
7 7 can go back to the base class and refactor the code for a cleaner inheritance
8 8 implementation that doesn't rely on so much monkeypatching.
9 9
10 10 But this lets us maintain a fully working IPython as we develop the new
11 11 machinery. This should thus be thought of as scaffolding.
12 12 """
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import inspect
20 20 import os
21 21 import re
22 22
23 23 # Our own
24 24 from IPython.core.interactiveshell import (
25 25 InteractiveShell, InteractiveShellABC
26 26 )
27 27 from IPython.core import page
28 28 from IPython.core.displayhook import DisplayHook
29 29 from IPython.core.macro import Macro
30 30 from IPython.core.payloadpage import install_payload_page
31 31 from IPython.utils import io
32 32 from IPython.utils.path import get_py_filename
33 33 from IPython.utils.text import StringTypes
34 34 from IPython.utils.traitlets import Instance, Type, Dict
35 35 from IPython.utils.warn import warn
36 36 from IPython.zmq.session import extract_header
37 37 from session import Session
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Globals and side-effects
41 41 #-----------------------------------------------------------------------------
42 42
43 43 # Install the payload version of page.
44 44 install_payload_page()
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Functions and classes
48 48 #-----------------------------------------------------------------------------
49 49
50 50 class ZMQDisplayHook(DisplayHook):
51 51
52 52 session = Instance(Session)
53 53 pub_socket = Instance('zmq.Socket')
54 54 parent_header = Dict({})
55 55
56 56 def set_parent(self, parent):
57 57 """Set the parent for outbound messages."""
58 58 self.parent_header = extract_header(parent)
59 59
60 60 def start_displayhook(self):
61 61 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
62 62
63 63 def write_output_prompt(self):
64 64 """Write the output prompt."""
65 65 if self.do_full_cache:
66 66 self.msg['content']['execution_count'] = self.prompt_count
67 67
68 68 def write_result_repr(self, result_repr):
69 69 self.msg['content']['data'] = result_repr
70 70
71 71 def finish_displayhook(self):
72 72 """Finish up all displayhook activities."""
73 73 self.pub_socket.send_json(self.msg)
74 74 self.msg = None
75 75
76 76
77 77 class ZMQInteractiveShell(InteractiveShell):
78 78 """A subclass of InteractiveShell for ZMQ."""
79 79
80 80 displayhook_class = Type(ZMQDisplayHook)
81 81
82 82 def init_environment(self):
83 83 """Configure the user's environment.
84 84
85 85 """
86 86 env = os.environ
87 87 # These two ensure 'ls' produces nice coloring on BSD-derived systems
88 88 env['TERM'] = 'xterm-color'
89 89 env['CLICOLOR'] = '1'
90 90 # Since normal pagers don't work at all (over pexpect we don't have
91 91 # single-key control of the subprocess), try to disable paging in
92 92 # subprocesses as much as possible.
93 93 env['PAGER'] = 'cat'
94 94 env['GIT_PAGER'] = 'cat'
95 95
96 96 def auto_rewrite_input(self, cmd):
97 97 """Called to show the auto-rewritten input for autocall and friends.
98 98
99 99 FIXME: this payload is currently not correctly processed by the
100 100 frontend.
101 101 """
102 102 new = self.displayhook.prompt1.auto_rewrite() + cmd
103 103 payload = dict(
104 104 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
105 105 transformed_input=new,
106 106 )
107 107 self.payload_manager.write_payload(payload)
108 108
109 109 def ask_exit(self):
110 110 """Engage the exit actions."""
111 111 payload = dict(
112 112 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
113 113 exit=True,
114 114 )
115 115 self.payload_manager.write_payload(payload)
116 116
117 117 def _showtraceback(self, etype, evalue, stb):
118 118
119 119 exc_content = {
120 120 u'traceback' : stb,
121 121 u'ename' : unicode(etype.__name__),
122 122 u'evalue' : unicode(evalue)
123 123 }
124 124
125 125 dh = self.displayhook
126 126 exc_msg = dh.session.msg(u'pyerr', exc_content, dh.parent_header)
127 127 # Send exception info over pub socket for other clients than the caller
128 128 # to pick up
129 129 dh.pub_socket.send_json(exc_msg)
130 130
131 131 # FIXME - Hack: store exception info in shell object. Right now, the
132 132 # caller is reading this info after the fact, we need to fix this logic
133 133 # to remove this hack. Even uglier, we need to store the error status
134 134 # here, because in the main loop, the logic that sets it is being
135 135 # skipped because runlines swallows the exceptions.
136 136 exc_content[u'status'] = u'error'
137 137 self._reply_content = exc_content
138 138 # /FIXME
139 139
140 140 return exc_content
141 141
142 142 #------------------------------------------------------------------------
143 143 # Magic overrides
144 144 #------------------------------------------------------------------------
145 145 # Once the base class stops inheriting from magic, this code needs to be
146 146 # moved into a separate machinery as well. For now, at least isolate here
147 147 # the magics which this class needs to implement differently from the base
148 148 # class, or that are unique to it.
149 149
150 150 def magic_doctest_mode(self,parameter_s=''):
151 151 """Toggle doctest mode on and off.
152 152
153 153 This mode is intended to make IPython behave as much as possible like a
154 154 plain Python shell, from the perspective of how its prompts, exceptions
155 155 and output look. This makes it easy to copy and paste parts of a
156 156 session into doctests. It does so by:
157 157
158 158 - Changing the prompts to the classic ``>>>`` ones.
159 159 - Changing the exception reporting mode to 'Plain'.
160 160 - Disabling pretty-printing of output.
161 161
162 162 Note that IPython also supports the pasting of code snippets that have
163 163 leading '>>>' and '...' prompts in them. This means that you can paste
164 164 doctests from files or docstrings (even if they have leading
165 165 whitespace), and the code will execute correctly. You can then use
166 166 '%history -t' to see the translated history; this will give you the
167 167 input after removal of all the leading prompts and whitespace, which
168 168 can be pasted back into an editor.
169 169
170 170 With these features, you can switch into this mode easily whenever you
171 171 need to do testing and changes to doctests, without having to leave
172 172 your existing IPython session.
173 173 """
174 174
175 175 from IPython.utils.ipstruct import Struct
176 176
177 177 # Shorthands
178 178 shell = self.shell
179 179 # dstore is a data store kept in the instance metadata bag to track any
180 180 # changes we make, so we can undo them later.
181 181 dstore = shell.meta.setdefault('doctest_mode', Struct())
182 182 save_dstore = dstore.setdefault
183 183
184 184 # save a few values we'll need to recover later
185 185 mode = save_dstore('mode', False)
186 186 save_dstore('rc_pprint', shell.pprint)
187 187 save_dstore('xmode', shell.InteractiveTB.mode)
188 188
189 189 if mode == False:
190 190 # turn on
191 191 shell.pprint = False
192 192 shell.magic_xmode('Plain')
193 193 else:
194 194 # turn off
195 195 shell.pprint = dstore.rc_pprint
196 196 shell.magic_xmode(dstore.xmode)
197 197
198 198 # Store new mode and inform on console
199 199 dstore.mode = bool(1-int(mode))
200 200 mode_label = ['OFF','ON'][dstore.mode]
201 201 print('Doctest mode is:', mode_label)
202 202
203 203 # Send the payload back so that clients can modify their prompt display
204 204 payload = dict(
205 205 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode',
206 206 mode=dstore.mode)
207 207 self.payload_manager.write_payload(payload)
208 208
209 209 def magic_edit(self,parameter_s='',last_call=['','']):
210 210 """Bring up an editor and execute the resulting code.
211 211
212 212 Usage:
213 213 %edit [options] [args]
214 214
215 215 %edit runs IPython's editor hook. The default version of this hook is
216 216 set to call the __IPYTHON__.rc.editor command. This is read from your
217 217 environment variable $EDITOR. If this isn't found, it will default to
218 218 vi under Linux/Unix and to notepad under Windows. See the end of this
219 219 docstring for how to change the editor hook.
220 220
221 221 You can also set the value of this editor via the command line option
222 222 '-editor' or in your ipythonrc file. This is useful if you wish to use
223 223 specifically for IPython an editor different from your typical default
224 224 (and for Windows users who typically don't set environment variables).
225 225
226 226 This command allows you to conveniently edit multi-line code right in
227 227 your IPython session.
228 228
229 229 If called without arguments, %edit opens up an empty editor with a
230 230 temporary file and will execute the contents of this file when you
231 231 close it (don't forget to save it!).
232 232
233 233
234 234 Options:
235 235
236 236 -n <number>: open the editor at a specified line number. By default,
237 237 the IPython editor hook uses the unix syntax 'editor +N filename', but
238 238 you can configure this by providing your own modified hook if your
239 239 favorite editor supports line-number specifications with a different
240 240 syntax.
241 241
242 242 -p: this will call the editor with the same data as the previous time
243 243 it was used, regardless of how long ago (in your current session) it
244 244 was.
245 245
246 246 -r: use 'raw' input. This option only applies to input taken from the
247 247 user's history. By default, the 'processed' history is used, so that
248 248 magics are loaded in their transformed version to valid Python. If
249 249 this option is given, the raw input as typed as the command line is
250 250 used instead. When you exit the editor, it will be executed by
251 251 IPython's own processor.
252 252
253 253 -x: do not execute the edited code immediately upon exit. This is
254 254 mainly useful if you are editing programs which need to be called with
255 255 command line arguments, which you can then do using %run.
256 256
257 257
258 258 Arguments:
259 259
260 260 If arguments are given, the following possibilites exist:
261 261
262 262 - The arguments are numbers or pairs of colon-separated numbers (like
263 263 1 4:8 9). These are interpreted as lines of previous input to be
264 264 loaded into the editor. The syntax is the same of the %macro command.
265 265
266 266 - If the argument doesn't start with a number, it is evaluated as a
267 267 variable and its contents loaded into the editor. You can thus edit
268 268 any string which contains python code (including the result of
269 269 previous edits).
270 270
271 271 - If the argument is the name of an object (other than a string),
272 272 IPython will try to locate the file where it was defined and open the
273 273 editor at the point where it is defined. You can use `%edit function`
274 274 to load an editor exactly at the point where 'function' is defined,
275 275 edit it and have the file be executed automatically.
276 276
277 277 If the object is a macro (see %macro for details), this opens up your
278 278 specified editor with a temporary file containing the macro's data.
279 279 Upon exit, the macro is reloaded with the contents of the file.
280 280
281 281 Note: opening at an exact line is only supported under Unix, and some
282 282 editors (like kedit and gedit up to Gnome 2.8) do not understand the
283 283 '+NUMBER' parameter necessary for this feature. Good editors like
284 284 (X)Emacs, vi, jed, pico and joe all do.
285 285
286 286 - If the argument is not found as a variable, IPython will look for a
287 287 file with that name (adding .py if necessary) and load it into the
288 288 editor. It will execute its contents with execfile() when you exit,
289 289 loading any code in the file into your interactive namespace.
290 290
291 291 After executing your code, %edit will return as output the code you
292 292 typed in the editor (except when it was an existing file). This way
293 293 you can reload the code in further invocations of %edit as a variable,
294 294 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
295 295 the output.
296 296
297 297 Note that %edit is also available through the alias %ed.
298 298
299 299 This is an example of creating a simple function inside the editor and
300 300 then modifying it. First, start up the editor:
301 301
302 302 In [1]: ed
303 303 Editing... done. Executing edited code...
304 304 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
305 305
306 306 We can then call the function foo():
307 307
308 308 In [2]: foo()
309 309 foo() was defined in an editing session
310 310
311 311 Now we edit foo. IPython automatically loads the editor with the
312 312 (temporary) file where foo() was previously defined:
313 313
314 314 In [3]: ed foo
315 315 Editing... done. Executing edited code...
316 316
317 317 And if we call foo() again we get the modified version:
318 318
319 319 In [4]: foo()
320 320 foo() has now been changed!
321 321
322 322 Here is an example of how to edit a code snippet successive
323 323 times. First we call the editor:
324 324
325 325 In [5]: ed
326 326 Editing... done. Executing edited code...
327 327 hello
328 328 Out[5]: "print 'hello'n"
329 329
330 330 Now we call it again with the previous output (stored in _):
331 331
332 332 In [6]: ed _
333 333 Editing... done. Executing edited code...
334 334 hello world
335 335 Out[6]: "print 'hello world'n"
336 336
337 337 Now we call it with the output #8 (stored in _8, also as Out[8]):
338 338
339 339 In [7]: ed _8
340 340 Editing... done. Executing edited code...
341 341 hello again
342 342 Out[7]: "print 'hello again'n"
343 343
344 344
345 345 Changing the default editor hook:
346 346
347 347 If you wish to write your own editor hook, you can put it in a
348 348 configuration file which you load at startup time. The default hook
349 349 is defined in the IPython.core.hooks module, and you can use that as a
350 350 starting example for further modifications. That file also has
351 351 general instructions on how to set a new hook for use once you've
352 352 defined it."""
353 353
354 354 # FIXME: This function has become a convoluted mess. It needs a
355 355 # ground-up rewrite with clean, simple logic.
356 356
357 357 def make_filename(arg):
358 358 "Make a filename from the given args"
359 359 try:
360 360 filename = get_py_filename(arg)
361 361 except IOError:
362 362 if args.endswith('.py'):
363 363 filename = arg
364 364 else:
365 365 filename = None
366 366 return filename
367 367
368 368 # custom exceptions
369 369 class DataIsObject(Exception): pass
370 370
371 371 opts,args = self.parse_options(parameter_s,'prn:')
372 372 # Set a few locals from the options for convenience:
373 373 opts_p = opts.has_key('p')
374 374 opts_r = opts.has_key('r')
375 375
376 376 # Default line number value
377 377 lineno = opts.get('n',None)
378 378 if lineno is not None:
379 379 try:
380 380 lineno = int(lineno)
381 381 except:
382 382 warn("The -n argument must be an integer.")
383 383 return
384 384
385 385 if opts_p:
386 386 args = '_%s' % last_call[0]
387 387 if not self.shell.user_ns.has_key(args):
388 388 args = last_call[1]
389 389
390 390 # use last_call to remember the state of the previous call, but don't
391 391 # let it be clobbered by successive '-p' calls.
392 392 try:
393 393 last_call[0] = self.shell.displayhook.prompt_count
394 394 if not opts_p:
395 395 last_call[1] = parameter_s
396 396 except:
397 397 pass
398 398
399 399 # by default this is done with temp files, except when the given
400 400 # arg is a filename
401 401 use_temp = 1
402 402
403 403 if re.match(r'\d',args):
404 404 # Mode where user specifies ranges of lines, like in %macro.
405 405 # This means that you can't edit files whose names begin with
406 406 # numbers this way. Tough.
407 407 ranges = args.split()
408 408 data = ''.join(self.extract_input_slices(ranges,opts_r))
409 409 elif args.endswith('.py'):
410 410 filename = make_filename(args)
411 411 data = ''
412 412 use_temp = 0
413 413 elif args:
414 414 try:
415 415 # Load the parameter given as a variable. If not a string,
416 416 # process it as an object instead (below)
417 417
418 418 #print '*** args',args,'type',type(args) # dbg
419 419 data = eval(args,self.shell.user_ns)
420 420 if not type(data) in StringTypes:
421 421 raise DataIsObject
422 422
423 423 except (NameError,SyntaxError):
424 424 # given argument is not a variable, try as a filename
425 425 filename = make_filename(args)
426 426 if filename is None:
427 427 warn("Argument given (%s) can't be found as a variable "
428 428 "or as a filename." % args)
429 429 return
430 430
431 431 data = ''
432 432 use_temp = 0
433 433 except DataIsObject:
434 434
435 435 # macros have a special edit function
436 436 if isinstance(data,Macro):
437 437 self._edit_macro(args,data)
438 438 return
439 439
440 440 # For objects, try to edit the file where they are defined
441 441 try:
442 442 filename = inspect.getabsfile(data)
443 443 if 'fakemodule' in filename.lower() and inspect.isclass(data):
444 444 # class created by %edit? Try to find source
445 445 # by looking for method definitions instead, the
446 446 # __module__ in those classes is FakeModule.
447 447 attrs = [getattr(data, aname) for aname in dir(data)]
448 448 for attr in attrs:
449 449 if not inspect.ismethod(attr):
450 450 continue
451 451 filename = inspect.getabsfile(attr)
452 452 if filename and 'fakemodule' not in filename.lower():
453 453 # change the attribute to be the edit target instead
454 454 data = attr
455 455 break
456 456
457 457 datafile = 1
458 458 except TypeError:
459 459 filename = make_filename(args)
460 460 datafile = 1
461 461 warn('Could not find file where `%s` is defined.\n'
462 462 'Opening a file named `%s`' % (args,filename))
463 463 # Now, make sure we can actually read the source (if it was in
464 464 # a temp file it's gone by now).
465 465 if datafile:
466 466 try:
467 467 if lineno is None:
468 468 lineno = inspect.getsourcelines(data)[1]
469 469 except IOError:
470 470 filename = make_filename(args)
471 471 if filename is None:
472 472 warn('The file `%s` where `%s` was defined cannot '
473 473 'be read.' % (filename,data))
474 474 return
475 475 use_temp = 0
476 476 else:
477 477 data = ''
478 478
479 479 if use_temp:
480 480 filename = self.shell.mktempfile(data)
481 481 print('IPython will make a temporary file named:', filename)
482 482
483 483 # Make sure we send to the client an absolute path, in case the working
484 484 # directory of client and kernel don't match
485 485 filename = os.path.abspath(filename)
486 486
487 487 payload = {
488 488 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
489 489 'filename' : filename,
490 490 'line_number' : lineno
491 491 }
492 492 self.payload_manager.write_payload(payload)
493 493
494 494 def magic_gui(self, *args, **kwargs):
495 495 raise NotImplementedError(
496 496 'GUI support must be enabled in command line options.')
497 497
498 498 def magic_pylab(self, *args, **kwargs):
499 499 raise NotImplementedError(
500 500 'pylab support must be enabled in command line options.')
501 501
502 502 # A few magics that are adapted to the specifics of using pexpect and a
503 503 # remote terminal
504 504
505 505 def magic_clear(self, arg_s):
506 506 """Clear the terminal."""
507 507 if os.name == 'posix':
508 508 self.shell.system("clear")
509 509 else:
510 510 self.shell.system("cls")
511 511
512 512 if os.name == 'nt':
513 513 # This is the usual name in windows
514 514 magic_cls = magic_clear
515 515
516 516 # Terminal pagers won't work over pexpect, but we do have our own pager
517 517
518 518 def magic_less(self, arg_s):
519 519 """Show a file through the pager.
520 520
521 521 Files ending in .py are syntax-highlighted."""
522 522 cont = open(arg_s).read()
523 523 if arg_s.endswith('.py'):
524 524 cont = self.shell.pycolorize(cont)
525 525 page.page(cont)
526 526
527 527 magic_more = magic_less
528 528
529 529 # Man calls a pager, so we also need to redefine it
530 530 if os.name == 'posix':
531 531 def magic_man(self, arg_s):
532 532 """Find the man page for the given command and display in pager."""
533 533 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
534 534 split=False))
535 535
536 536 # FIXME: this is specific to the GUI, so we should let the gui app load
537 537 # magics at startup that are only for the gui. Once the gui app has proper
538 538 # profile and configuration management, we can have it initialize a kernel
539 539 # with a special config file that provides these.
540 540 def magic_guiref(self, arg_s):
541 541 """Show a basic reference about the GUI console."""
542 542 from IPython.core.usage import gui_reference
543 543 page.page(gui_reference, auto_html=True)
544 544
545 def magic_loadpy(self, arg_s):
546 """Load a .py python script into the GUI console.
547
548 This magic command can either take a local filename or a url::
549
550 %loadpy myscript.py
551 %loadpy http://www.example.com/myscript.py
552 """
553 if not arg_s.endswith('.py'):
554 raise ValueError('%%load only works with .py files: %s' % arg_s)
555 if arg_s.startswith('http'):
556 import urllib2
557 response = urllib2.urlopen(arg_s)
558 content = response.read()
559 else:
560 content = open(arg_s).read()
561 payload = dict(
562 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_loadpy',
563 text=content
564 )
565 self.payload_manager.write_payload(payload)
545 566
546 567 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now