##// END OF EJS Templates
update payload source...
MinRK -
Show More
@@ -1,96 +1,96 b''
1 1 # encoding: utf-8
2 2 """
3 3 A payload based version of page.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Fernando Perez
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # Third-party
23 23 try:
24 24 from docutils.core import publish_string
25 25 except ImportError:
26 26 # html paging won't be available, but we don't raise any errors. It's a
27 27 # purely optional feature.
28 28 pass
29 29
30 30 # Our own
31 31 from IPython.core.interactiveshell import InteractiveShell
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Classes and functions
35 35 #-----------------------------------------------------------------------------
36 36
37 37 def page(strng, start=0, screen_lines=0, pager_cmd=None,
38 38 html=None, auto_html=False):
39 39 """Print a string, piping through a pager.
40 40
41 41 This version ignores the screen_lines and pager_cmd arguments and uses
42 42 IPython's payload system instead.
43 43
44 44 Parameters
45 45 ----------
46 46 strng : str
47 47 Text to page.
48 48
49 49 start : int
50 50 Starting line at which to place the display.
51 51
52 52 html : str, optional
53 53 If given, an html string to send as well.
54 54
55 55 auto_html : bool, optional
56 56 If true, the input string is assumed to be valid reStructuredText and is
57 57 converted to HTML with docutils. Note that if docutils is not found,
58 58 this option is silently ignored.
59 59
60 60 Notes
61 61 -----
62 62
63 63 Only one of the ``html`` and ``auto_html`` options can be given, not
64 64 both.
65 65 """
66 66
67 67 # Some routines may auto-compute start offsets incorrectly and pass a
68 68 # negative value. Offset to 0 for robustness.
69 69 start = max(0, start)
70 70 shell = InteractiveShell.instance()
71 71
72 72 if auto_html:
73 73 try:
74 74 # These defaults ensure user configuration variables for docutils
75 75 # are not loaded, only our config is used here.
76 76 defaults = {'file_insertion_enabled': 0,
77 77 'raw_enabled': 0,
78 78 '_disable_config': 1}
79 79 html = publish_string(strng, writer_name='html',
80 80 settings_overrides=defaults)
81 81 except:
82 82 pass
83 83
84 84 payload = dict(
85 source='IPython.kernel.zmq.page.page',
85 source='page',
86 86 text=strng,
87 87 html=html,
88 88 start_line_number=start
89 89 )
90 90 shell.payload_manager.write_payload(payload)
91 91
92 92
93 93 def install_payload_page():
94 94 """Install this version of page as IPython.core.page.page."""
95 95 from IPython.core import page as corepage
96 96 corepage.page = page
@@ -1,501 +1,501 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Kernel
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule Kernel
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19
20 20 var utils = IPython.utils;
21 21
22 22 // Initialization and connection.
23 23 /**
24 24 * A Kernel Class to communicate with the Python kernel
25 25 * @Class Kernel
26 26 */
27 27 var Kernel = function (base_url) {
28 28 this.kernel_id = null;
29 29 this.shell_channel = null;
30 30 this.iopub_channel = null;
31 31 this.stdin_channel = null;
32 32 this.base_url = base_url;
33 33 this.running = false;
34 34 this.username = "username";
35 35 this.session_id = utils.uuid();
36 36 this._msg_callbacks = {};
37 37
38 38 if (typeof(WebSocket) !== 'undefined') {
39 39 this.WebSocket = WebSocket;
40 40 } else if (typeof(MozWebSocket) !== 'undefined') {
41 41 this.WebSocket = MozWebSocket;
42 42 } else {
43 43 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
44 44 };
45 45 this.bind_events();
46 46 };
47 47
48 48
49 49 Kernel.prototype._get_msg = function (msg_type, content) {
50 50 var msg = {
51 51 header : {
52 52 msg_id : utils.uuid(),
53 53 username : this.username,
54 54 session : this.session_id,
55 55 msg_type : msg_type
56 56 },
57 57 metadata : {},
58 58 content : content,
59 59 parent_header : {}
60 60 };
61 61 return msg;
62 62 };
63 63
64 64 Kernel.prototype.bind_events = function() {
65 65 var that = this;
66 66 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
67 67 that.send_input_reply(data);
68 68 });
69 69 }
70 70
71 71 /**
72 72 * Start the Python kernel
73 73 * @method start
74 74 */
75 75 Kernel.prototype.start = function (notebook_id) {
76 76 var that = this;
77 77 if (!this.running) {
78 78 var qs = $.param({notebook:notebook_id});
79 79 var url = this.base_url + '?' + qs;
80 80 $.post(url,
81 81 $.proxy(that._kernel_started,that),
82 82 'json'
83 83 );
84 84 };
85 85 };
86 86
87 87 /**
88 88 * Restart the python kernel.
89 89 *
90 90 * Emit a 'status_restarting.Kernel' event with
91 91 * the current object as parameter
92 92 *
93 93 * @method restart
94 94 */
95 95 Kernel.prototype.restart = function () {
96 96 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
97 97 var that = this;
98 98 if (this.running) {
99 99 this.stop_channels();
100 100 var url = this.kernel_url + "/restart";
101 101 $.post(url,
102 102 $.proxy(that._kernel_started, that),
103 103 'json'
104 104 );
105 105 };
106 106 };
107 107
108 108
109 109 Kernel.prototype._kernel_started = function (json) {
110 110 console.log("Kernel started: ", json.kernel_id);
111 111 this.running = true;
112 112 this.kernel_id = json.kernel_id;
113 113 var ws_url = json.ws_url;
114 114 if (ws_url.match(/wss?:\/\//) == null) {
115 115 // trailing 's' in https will become wss for secure web sockets
116 116 prot = location.protocol.replace('http', 'ws') + "//";
117 117 ws_url = prot + location.host + ws_url;
118 118 };
119 119 this.ws_url = ws_url;
120 120 this.kernel_url = this.base_url + "/" + this.kernel_id;
121 121 this.start_channels();
122 122 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
123 123 };
124 124
125 125
126 126 Kernel.prototype._websocket_closed = function(ws_url, early) {
127 127 this.stop_channels();
128 128 $([IPython.events]).trigger('websocket_closed.Kernel',
129 129 {ws_url: ws_url, kernel: this, early: early}
130 130 );
131 131 };
132 132
133 133 /**
134 134 * Start the `shell`and `iopub` channels.
135 135 * Will stop and restart them if they already exist.
136 136 *
137 137 * @method start_channels
138 138 */
139 139 Kernel.prototype.start_channels = function () {
140 140 var that = this;
141 141 this.stop_channels();
142 142 var ws_url = this.ws_url + this.kernel_url;
143 143 console.log("Starting WebSockets:", ws_url);
144 144 this.shell_channel = new this.WebSocket(ws_url + "/shell");
145 145 this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
146 146 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
147 147 send_cookie = function(){
148 148 // send the session id so the Session object Python-side
149 149 // has the same identity
150 150 this.send(that.session_id + ':' + document.cookie);
151 151 };
152 152 var already_called_onclose = false; // only alert once
153 153 var ws_closed_early = function(evt){
154 154 if (already_called_onclose){
155 155 return;
156 156 }
157 157 already_called_onclose = true;
158 158 if ( ! evt.wasClean ){
159 159 that._websocket_closed(ws_url, true);
160 160 }
161 161 };
162 162 var ws_closed_late = function(evt){
163 163 if (already_called_onclose){
164 164 return;
165 165 }
166 166 already_called_onclose = true;
167 167 if ( ! evt.wasClean ){
168 168 that._websocket_closed(ws_url, false);
169 169 }
170 170 };
171 171 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
172 172 for (var i=0; i < channels.length; i++) {
173 173 channels[i].onopen = send_cookie;
174 174 channels[i].onclose = ws_closed_early;
175 175 }
176 176 // switch from early-close to late-close message after 1s
177 177 setTimeout(function() {
178 178 for (var i=0; i < channels.length; i++) {
179 179 if (channels[i] !== null) {
180 180 channels[i].onclose = ws_closed_late;
181 181 }
182 182 }
183 183 }, 1000);
184 184 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
185 185 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this);
186 186 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
187 187 };
188 188
189 189 /**
190 190 * Start the `shell`and `iopub` channels.
191 191 * @method stop_channels
192 192 */
193 193 Kernel.prototype.stop_channels = function () {
194 194 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
195 195 for (var i=0; i < channels.length; i++) {
196 196 if ( channels[i] !== null ) {
197 197 channels[i].onclose = function (evt) {};
198 198 channels[i].close();
199 199 }
200 200 };
201 201 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
202 202 };
203 203
204 204 // Main public methods.
205 205
206 206 /**
207 207 * Get info on object asynchronoulsy
208 208 *
209 209 * @async
210 210 * @param objname {string}
211 211 * @param callback {dict}
212 212 * @method object_info_request
213 213 *
214 214 * @example
215 215 *
216 216 * When calling this method pass a callbacks structure of the form:
217 217 *
218 218 * callbacks = {
219 219 * 'object_info_reply': object_info_reply_callback
220 220 * }
221 221 *
222 222 * The `object_info_reply_callback` will be passed the content object of the
223 223 *
224 224 * `object_into_reply` message documented in
225 225 * [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
226 226 */
227 227 Kernel.prototype.object_info_request = function (objname, callbacks) {
228 228 if(typeof(objname)!=null && objname!=null)
229 229 {
230 230 var content = {
231 231 oname : objname.toString(),
232 232 detail_level : 0,
233 233 };
234 234 var msg = this._get_msg("object_info_request", content);
235 235 this.shell_channel.send(JSON.stringify(msg));
236 236 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
237 237 return msg.header.msg_id;
238 238 }
239 239 return;
240 240 }
241 241
242 242 /**
243 243 * Execute given code into kernel, and pass result to callback.
244 244 *
245 245 * TODO: document input_request in callbacks
246 246 *
247 247 * @async
248 248 * @method execute
249 249 * @param {string} code
250 250 * @param [callbacks] {Object} With the optional following keys
251 251 * @param callbacks.'execute_reply' {function}
252 252 * @param callbacks.'output' {function}
253 253 * @param callbacks.'clear_output' {function}
254 254 * @param callbacks.'set_next_input' {function}
255 255 * @param {object} [options]
256 256 * @param [options.silent=false] {Boolean}
257 257 * @param [options.user_expressions=empty_dict] {Dict}
258 258 * @param [options.user_variables=empty_list] {List od Strings}
259 259 * @param [options.allow_stdin=false] {Boolean} true|false
260 260 *
261 261 * @example
262 262 *
263 263 * The options object should contain the options for the execute call. Its default
264 264 * values are:
265 265 *
266 266 * options = {
267 267 * silent : true,
268 268 * user_variables : [],
269 269 * user_expressions : {},
270 270 * allow_stdin : false
271 271 * }
272 272 *
273 273 * When calling this method pass a callbacks structure of the form:
274 274 *
275 275 * callbacks = {
276 276 * 'execute_reply': execute_reply_callback,
277 277 * 'output': output_callback,
278 278 * 'clear_output': clear_output_callback,
279 279 * 'set_next_input': set_next_input_callback
280 280 * }
281 281 *
282 282 * The `execute_reply_callback` will be passed the content and metadata
283 283 * objects of the `execute_reply` message documented
284 284 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute)
285 285 *
286 286 * The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr')
287 287 * of the output and the content and metadata objects of the PUB/SUB channel that contains the
288 288 * output:
289 289 *
290 290 * http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
291 291 *
292 292 * The `clear_output_callback` will be passed a content object that contains
293 293 * stdout, stderr and other fields that are booleans, as well as the metadata object.
294 294 *
295 295 * The `set_next_input_callback` will be passed the text that should become the next
296 296 * input cell.
297 297 */
298 298 Kernel.prototype.execute = function (code, callbacks, options) {
299 299
300 300 var content = {
301 301 code : code,
302 302 silent : true,
303 303 user_variables : [],
304 304 user_expressions : {},
305 305 allow_stdin : false
306 306 };
307 307 callbacks = callbacks || {};
308 308 if (callbacks.input_request !== undefined) {
309 309 content.allow_stdin = true;
310 310 }
311 311 $.extend(true, content, options)
312 312 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
313 313 var msg = this._get_msg("execute_request", content);
314 314 this.shell_channel.send(JSON.stringify(msg));
315 315 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
316 316 return msg.header.msg_id;
317 317 };
318 318
319 319 /**
320 320 * When calling this method pass a callbacks structure of the form:
321 321 *
322 322 * callbacks = {
323 323 * 'complete_reply': complete_reply_callback
324 324 * }
325 325 *
326 326 * The `complete_reply_callback` will be passed the content object of the
327 327 * `complete_reply` message documented
328 328 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
329 329 *
330 330 * @method complete
331 331 * @param line {integer}
332 332 * @param cursor_pos {integer}
333 333 * @param {dict} callbacks
334 334 * @param callbacks.complete_reply {function} `complete_reply_callback`
335 335 *
336 336 */
337 337 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
338 338 callbacks = callbacks || {};
339 339 var content = {
340 340 text : '',
341 341 line : line,
342 342 block : null,
343 343 cursor_pos : cursor_pos
344 344 };
345 345 var msg = this._get_msg("complete_request", content);
346 346 this.shell_channel.send(JSON.stringify(msg));
347 347 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
348 348 return msg.header.msg_id;
349 349 };
350 350
351 351
352 352 Kernel.prototype.interrupt = function () {
353 353 if (this.running) {
354 354 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
355 355 $.post(this.kernel_url + "/interrupt");
356 356 };
357 357 };
358 358
359 359
360 360 Kernel.prototype.kill = function () {
361 361 if (this.running) {
362 362 this.running = false;
363 363 var settings = {
364 364 cache : false,
365 365 type : "DELETE"
366 366 };
367 367 $.ajax(this.kernel_url, settings);
368 368 };
369 369 };
370 370
371 371 Kernel.prototype.send_input_reply = function (input) {
372 372 var content = {
373 373 value : input,
374 374 };
375 375 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
376 376 var msg = this._get_msg("input_reply", content);
377 377 this.stdin_channel.send(JSON.stringify(msg));
378 378 return msg.header.msg_id;
379 379 };
380 380
381 381
382 382 // Reply handlers
383 383
384 384 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
385 385 var callbacks = this._msg_callbacks[msg_id];
386 386 return callbacks;
387 387 };
388 388
389 389
390 390 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
391 391 this._msg_callbacks[msg_id] = callbacks || {};
392 392 }
393 393
394 394
395 395 Kernel.prototype._handle_shell_reply = function (e) {
396 396 var reply = $.parseJSON(e.data);
397 397 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
398 398 var header = reply.header;
399 399 var content = reply.content;
400 400 var metadata = reply.metadata;
401 401 var msg_type = header.msg_type;
402 402 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
403 403 if (callbacks !== undefined) {
404 404 var cb = callbacks[msg_type];
405 405 if (cb !== undefined) {
406 406 cb(content, metadata);
407 407 }
408 408 };
409 409
410 410 if (content.payload !== undefined) {
411 411 var payload = content.payload || [];
412 412 this._handle_payload(callbacks, payload);
413 413 }
414 414 };
415 415
416 416
417 417 Kernel.prototype._handle_payload = function (callbacks, payload) {
418 418 var l = payload.length;
419 419 // Payloads are handled by triggering events because we don't want the Kernel
420 420 // to depend on the Notebook or Pager classes.
421 421 for (var i=0; i<l; i++) {
422 if (payload[i].source === 'IPython.kernel.zmq.page.page') {
422 if (payload[i].source === 'page') {
423 423 var data = {'text':payload[i].text}
424 424 $([IPython.events]).trigger('open_with_text.Pager', data);
425 } else if (payload[i].source === 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
425 } else if (payload[i].source === 'set_next_input') {
426 426 if (callbacks.set_next_input !== undefined) {
427 427 callbacks.set_next_input(payload[i].text)
428 428 }
429 429 }
430 430 };
431 431 };
432 432
433 433
434 434 Kernel.prototype._handle_iopub_reply = function (e) {
435 435 var reply = $.parseJSON(e.data);
436 436 var content = reply.content;
437 437 var msg_type = reply.header.msg_type;
438 438 var metadata = reply.metadata;
439 439 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
440 440 if (msg_type !== 'status' && callbacks === undefined) {
441 441 // Message not from one of this notebook's cells and there are no
442 442 // callbacks to handle it.
443 443 return;
444 444 }
445 445 var output_types = ['stream','display_data','pyout','pyerr'];
446 446 if (output_types.indexOf(msg_type) >= 0) {
447 447 var cb = callbacks['output'];
448 448 if (cb !== undefined) {
449 449 cb(msg_type, content, metadata);
450 450 }
451 451 } else if (msg_type === 'status') {
452 452 if (content.execution_state === 'busy') {
453 453 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
454 454 } else if (content.execution_state === 'idle') {
455 455 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
456 456 } else if (content.execution_state === 'restarting') {
457 457 // autorestarting is distinct from restarting,
458 458 // in that it means the kernel died and the server is restarting it.
459 459 // status_restarting sets the notification widget,
460 460 // autorestart shows the more prominent dialog.
461 461 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
462 462 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
463 463 } else if (content.execution_state === 'dead') {
464 464 this.stop_channels();
465 465 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
466 466 };
467 467 } else if (msg_type === 'clear_output') {
468 468 var cb = callbacks['clear_output'];
469 469 if (cb !== undefined) {
470 470 cb(content, metadata);
471 471 }
472 472 };
473 473 };
474 474
475 475
476 476 Kernel.prototype._handle_input_request = function (e) {
477 477 var request = $.parseJSON(e.data);
478 478 var header = request.header;
479 479 var content = request.content;
480 480 var metadata = request.metadata;
481 481 var msg_type = header.msg_type;
482 482 if (msg_type !== 'input_request') {
483 483 console.log("Invalid input request!", request);
484 484 return;
485 485 }
486 486 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
487 487 if (callbacks !== undefined) {
488 488 var cb = callbacks[msg_type];
489 489 if (cb !== undefined) {
490 490 cb(content, metadata);
491 491 }
492 492 };
493 493 };
494 494
495 495
496 496 IPython.Kernel = Kernel;
497 497
498 498 return IPython;
499 499
500 500 }(IPython));
501 501
@@ -1,602 +1,602 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 os
20 20 import sys
21 21 import time
22 22
23 23 # System library imports
24 24 from zmq.eventloop import ioloop
25 25
26 26 # Our own
27 27 from IPython.core.interactiveshell import (
28 28 InteractiveShell, InteractiveShellABC
29 29 )
30 30 from IPython.core import page
31 31 from IPython.core.autocall import ZMQExitAutocall
32 32 from IPython.core.displaypub import DisplayPublisher
33 33 from IPython.core.error import UsageError
34 34 from IPython.core.magics import MacroToEdit, CodeMagics
35 35 from IPython.core.magic import magics_class, line_magic, Magics
36 36 from IPython.core.payloadpage import install_payload_page
37 37 from IPython.display import display, Javascript
38 38 from IPython.kernel.inprocess.socket import SocketABC
39 39 from IPython.kernel import (
40 40 get_connection_file, get_connection_info, connect_qtconsole
41 41 )
42 42 from IPython.testing.skipdoctest import skip_doctest
43 43 from IPython.utils import openpy
44 44 from IPython.utils.jsonutil import json_clean, encode_images
45 45 from IPython.utils.process import arg_split
46 46 from IPython.utils import py3compat
47 47 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
48 48 from IPython.utils.warn import error
49 49 from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook
50 50 from IPython.kernel.zmq.datapub import ZMQDataPublisher
51 51 from IPython.kernel.zmq.session import extract_header
52 52 from session import Session
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Functions and classes
56 56 #-----------------------------------------------------------------------------
57 57
58 58 class ZMQDisplayPublisher(DisplayPublisher):
59 59 """A display publisher that publishes data using a ZeroMQ PUB socket."""
60 60
61 61 session = Instance(Session)
62 62 pub_socket = Instance(SocketABC)
63 63 parent_header = Dict({})
64 64 topic = CBytes(b'display_data')
65 65
66 66 def set_parent(self, parent):
67 67 """Set the parent for outbound messages."""
68 68 self.parent_header = extract_header(parent)
69 69
70 70 def _flush_streams(self):
71 71 """flush IO Streams prior to display"""
72 72 sys.stdout.flush()
73 73 sys.stderr.flush()
74 74
75 75 def publish(self, source, data, metadata=None):
76 76 self._flush_streams()
77 77 if metadata is None:
78 78 metadata = {}
79 79 self._validate_data(source, data, metadata)
80 80 content = {}
81 81 content['source'] = source
82 82 content['data'] = encode_images(data)
83 83 content['metadata'] = metadata
84 84 self.session.send(
85 85 self.pub_socket, u'display_data', json_clean(content),
86 86 parent=self.parent_header, ident=self.topic,
87 87 )
88 88
89 89 def clear_output(self, stdout=True, stderr=True, other=True):
90 90 content = dict(stdout=stdout, stderr=stderr, other=other)
91 91
92 92 if stdout:
93 93 print('\r', file=sys.stdout, end='')
94 94 if stderr:
95 95 print('\r', file=sys.stderr, end='')
96 96
97 97 self._flush_streams()
98 98
99 99 self.session.send(
100 100 self.pub_socket, u'clear_output', content,
101 101 parent=self.parent_header, ident=self.topic,
102 102 )
103 103
104 104 @magics_class
105 105 class KernelMagics(Magics):
106 106 #------------------------------------------------------------------------
107 107 # Magic overrides
108 108 #------------------------------------------------------------------------
109 109 # Once the base class stops inheriting from magic, this code needs to be
110 110 # moved into a separate machinery as well. For now, at least isolate here
111 111 # the magics which this class needs to implement differently from the base
112 112 # class, or that are unique to it.
113 113
114 114 @line_magic
115 115 def doctest_mode(self, parameter_s=''):
116 116 """Toggle doctest mode on and off.
117 117
118 118 This mode is intended to make IPython behave as much as possible like a
119 119 plain Python shell, from the perspective of how its prompts, exceptions
120 120 and output look. This makes it easy to copy and paste parts of a
121 121 session into doctests. It does so by:
122 122
123 123 - Changing the prompts to the classic ``>>>`` ones.
124 124 - Changing the exception reporting mode to 'Plain'.
125 125 - Disabling pretty-printing of output.
126 126
127 127 Note that IPython also supports the pasting of code snippets that have
128 128 leading '>>>' and '...' prompts in them. This means that you can paste
129 129 doctests from files or docstrings (even if they have leading
130 130 whitespace), and the code will execute correctly. You can then use
131 131 '%history -t' to see the translated history; this will give you the
132 132 input after removal of all the leading prompts and whitespace, which
133 133 can be pasted back into an editor.
134 134
135 135 With these features, you can switch into this mode easily whenever you
136 136 need to do testing and changes to doctests, without having to leave
137 137 your existing IPython session.
138 138 """
139 139
140 140 from IPython.utils.ipstruct import Struct
141 141
142 142 # Shorthands
143 143 shell = self.shell
144 144 disp_formatter = self.shell.display_formatter
145 145 ptformatter = disp_formatter.formatters['text/plain']
146 146 # dstore is a data store kept in the instance metadata bag to track any
147 147 # changes we make, so we can undo them later.
148 148 dstore = shell.meta.setdefault('doctest_mode', Struct())
149 149 save_dstore = dstore.setdefault
150 150
151 151 # save a few values we'll need to recover later
152 152 mode = save_dstore('mode', False)
153 153 save_dstore('rc_pprint', ptformatter.pprint)
154 154 save_dstore('rc_active_types',disp_formatter.active_types)
155 155 save_dstore('xmode', shell.InteractiveTB.mode)
156 156
157 157 if mode == False:
158 158 # turn on
159 159 ptformatter.pprint = False
160 160 disp_formatter.active_types = ['text/plain']
161 161 shell.magic('xmode Plain')
162 162 else:
163 163 # turn off
164 164 ptformatter.pprint = dstore.rc_pprint
165 165 disp_formatter.active_types = dstore.rc_active_types
166 166 shell.magic("xmode " + dstore.xmode)
167 167
168 168 # Store new mode and inform on console
169 169 dstore.mode = bool(1-int(mode))
170 170 mode_label = ['OFF','ON'][dstore.mode]
171 171 print('Doctest mode is:', mode_label)
172 172
173 173 # Send the payload back so that clients can modify their prompt display
174 174 payload = dict(
175 source='IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.doctest_mode',
175 source='doctest_mode',
176 176 mode=dstore.mode)
177 177 shell.payload_manager.write_payload(payload)
178 178
179 179
180 180 _find_edit_target = CodeMagics._find_edit_target
181 181
182 182 @skip_doctest
183 183 @line_magic
184 184 def edit(self, parameter_s='', last_call=['','']):
185 185 """Bring up an editor and execute the resulting code.
186 186
187 187 Usage:
188 188 %edit [options] [args]
189 189
190 190 %edit runs an external text editor. You will need to set the command for
191 191 this editor via the ``TerminalInteractiveShell.editor`` option in your
192 192 configuration file before it will work.
193 193
194 194 This command allows you to conveniently edit multi-line code right in
195 195 your IPython session.
196 196
197 197 If called without arguments, %edit opens up an empty editor with a
198 198 temporary file and will execute the contents of this file when you
199 199 close it (don't forget to save it!).
200 200
201 201
202 202 Options:
203 203
204 204 -n <number>: open the editor at a specified line number. By default,
205 205 the IPython editor hook uses the unix syntax 'editor +N filename', but
206 206 you can configure this by providing your own modified hook if your
207 207 favorite editor supports line-number specifications with a different
208 208 syntax.
209 209
210 210 -p: this will call the editor with the same data as the previous time
211 211 it was used, regardless of how long ago (in your current session) it
212 212 was.
213 213
214 214 -r: use 'raw' input. This option only applies to input taken from the
215 215 user's history. By default, the 'processed' history is used, so that
216 216 magics are loaded in their transformed version to valid Python. If
217 217 this option is given, the raw input as typed as the command line is
218 218 used instead. When you exit the editor, it will be executed by
219 219 IPython's own processor.
220 220
221 221 -x: do not execute the edited code immediately upon exit. This is
222 222 mainly useful if you are editing programs which need to be called with
223 223 command line arguments, which you can then do using %run.
224 224
225 225
226 226 Arguments:
227 227
228 228 If arguments are given, the following possibilites exist:
229 229
230 230 - The arguments are numbers or pairs of colon-separated numbers (like
231 231 1 4:8 9). These are interpreted as lines of previous input to be
232 232 loaded into the editor. The syntax is the same of the %macro command.
233 233
234 234 - If the argument doesn't start with a number, it is evaluated as a
235 235 variable and its contents loaded into the editor. You can thus edit
236 236 any string which contains python code (including the result of
237 237 previous edits).
238 238
239 239 - If the argument is the name of an object (other than a string),
240 240 IPython will try to locate the file where it was defined and open the
241 241 editor at the point where it is defined. You can use `%edit function`
242 242 to load an editor exactly at the point where 'function' is defined,
243 243 edit it and have the file be executed automatically.
244 244
245 245 If the object is a macro (see %macro for details), this opens up your
246 246 specified editor with a temporary file containing the macro's data.
247 247 Upon exit, the macro is reloaded with the contents of the file.
248 248
249 249 Note: opening at an exact line is only supported under Unix, and some
250 250 editors (like kedit and gedit up to Gnome 2.8) do not understand the
251 251 '+NUMBER' parameter necessary for this feature. Good editors like
252 252 (X)Emacs, vi, jed, pico and joe all do.
253 253
254 254 - If the argument is not found as a variable, IPython will look for a
255 255 file with that name (adding .py if necessary) and load it into the
256 256 editor. It will execute its contents with execfile() when you exit,
257 257 loading any code in the file into your interactive namespace.
258 258
259 259 After executing your code, %edit will return as output the code you
260 260 typed in the editor (except when it was an existing file). This way
261 261 you can reload the code in further invocations of %edit as a variable,
262 262 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
263 263 the output.
264 264
265 265 Note that %edit is also available through the alias %ed.
266 266
267 267 This is an example of creating a simple function inside the editor and
268 268 then modifying it. First, start up the editor:
269 269
270 270 In [1]: ed
271 271 Editing... done. Executing edited code...
272 272 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
273 273
274 274 We can then call the function foo():
275 275
276 276 In [2]: foo()
277 277 foo() was defined in an editing session
278 278
279 279 Now we edit foo. IPython automatically loads the editor with the
280 280 (temporary) file where foo() was previously defined:
281 281
282 282 In [3]: ed foo
283 283 Editing... done. Executing edited code...
284 284
285 285 And if we call foo() again we get the modified version:
286 286
287 287 In [4]: foo()
288 288 foo() has now been changed!
289 289
290 290 Here is an example of how to edit a code snippet successive
291 291 times. First we call the editor:
292 292
293 293 In [5]: ed
294 294 Editing... done. Executing edited code...
295 295 hello
296 296 Out[5]: "print 'hello'n"
297 297
298 298 Now we call it again with the previous output (stored in _):
299 299
300 300 In [6]: ed _
301 301 Editing... done. Executing edited code...
302 302 hello world
303 303 Out[6]: "print 'hello world'n"
304 304
305 305 Now we call it with the output #8 (stored in _8, also as Out[8]):
306 306
307 307 In [7]: ed _8
308 308 Editing... done. Executing edited code...
309 309 hello again
310 310 Out[7]: "print 'hello again'n"
311 311 """
312 312
313 313 opts,args = self.parse_options(parameter_s,'prn:')
314 314
315 315 try:
316 316 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
317 317 except MacroToEdit as e:
318 318 # TODO: Implement macro editing over 2 processes.
319 319 print("Macro editing not yet implemented in 2-process model.")
320 320 return
321 321
322 322 # Make sure we send to the client an absolute path, in case the working
323 323 # directory of client and kernel don't match
324 324 filename = os.path.abspath(filename)
325 325
326 326 payload = {
327 'source' : 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
327 'source' : 'edit_magic',
328 328 'filename' : filename,
329 329 'line_number' : lineno
330 330 }
331 331 self.shell.payload_manager.write_payload(payload)
332 332
333 333 # A few magics that are adapted to the specifics of using pexpect and a
334 334 # remote terminal
335 335
336 336 @line_magic
337 337 def clear(self, arg_s):
338 338 """Clear the terminal."""
339 339 if os.name == 'posix':
340 340 self.shell.system("clear")
341 341 else:
342 342 self.shell.system("cls")
343 343
344 344 if os.name == 'nt':
345 345 # This is the usual name in windows
346 346 cls = line_magic('cls')(clear)
347 347
348 348 # Terminal pagers won't work over pexpect, but we do have our own pager
349 349
350 350 @line_magic
351 351 def less(self, arg_s):
352 352 """Show a file through the pager.
353 353
354 354 Files ending in .py are syntax-highlighted."""
355 355 if not arg_s:
356 356 raise UsageError('Missing filename.')
357 357
358 358 cont = open(arg_s).read()
359 359 if arg_s.endswith('.py'):
360 360 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
361 361 else:
362 362 cont = open(arg_s).read()
363 363 page.page(cont)
364 364
365 365 more = line_magic('more')(less)
366 366
367 367 # Man calls a pager, so we also need to redefine it
368 368 if os.name == 'posix':
369 369 @line_magic
370 370 def man(self, arg_s):
371 371 """Find the man page for the given command and display in pager."""
372 372 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
373 373 split=False))
374 374
375 375 @line_magic
376 376 def connect_info(self, arg_s):
377 377 """Print information for connecting other clients to this kernel
378 378
379 379 It will print the contents of this session's connection file, as well as
380 380 shortcuts for local clients.
381 381
382 382 In the simplest case, when called from the most recently launched kernel,
383 383 secondary clients can be connected, simply with:
384 384
385 385 $> ipython <app> --existing
386 386
387 387 """
388 388
389 389 from IPython.core.application import BaseIPythonApplication as BaseIPApp
390 390
391 391 if BaseIPApp.initialized():
392 392 app = BaseIPApp.instance()
393 393 security_dir = app.profile_dir.security_dir
394 394 profile = app.profile
395 395 else:
396 396 profile = 'default'
397 397 security_dir = ''
398 398
399 399 try:
400 400 connection_file = get_connection_file()
401 401 info = get_connection_info(unpack=False)
402 402 except Exception as e:
403 403 error("Could not get connection info: %r" % e)
404 404 return
405 405
406 406 # add profile flag for non-default profile
407 407 profile_flag = "--profile %s" % profile if profile != 'default' else ""
408 408
409 409 # if it's in the security dir, truncate to basename
410 410 if security_dir == os.path.dirname(connection_file):
411 411 connection_file = os.path.basename(connection_file)
412 412
413 413
414 414 print (info + '\n')
415 415 print ("Paste the above JSON into a file, and connect with:\n"
416 416 " $> ipython <app> --existing <file>\n"
417 417 "or, if you are local, you can connect with just:\n"
418 418 " $> ipython <app> --existing {0} {1}\n"
419 419 "or even just:\n"
420 420 " $> ipython <app> --existing {1}\n"
421 421 "if this is the most recent IPython session you have started.".format(
422 422 connection_file, profile_flag
423 423 )
424 424 )
425 425
426 426 @line_magic
427 427 def qtconsole(self, arg_s):
428 428 """Open a qtconsole connected to this kernel.
429 429
430 430 Useful for connecting a qtconsole to running notebooks, for better
431 431 debugging.
432 432 """
433 433
434 434 # %qtconsole should imply bind_kernel for engines:
435 435 try:
436 436 from IPython.parallel import bind_kernel
437 437 except ImportError:
438 438 # technically possible, because parallel has higher pyzmq min-version
439 439 pass
440 440 else:
441 441 bind_kernel()
442 442
443 443 try:
444 444 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
445 445 except Exception as e:
446 446 error("Could not start qtconsole: %r" % e)
447 447 return
448 448
449 449 @line_magic
450 450 def autosave(self, arg_s):
451 451 """Set the autosave interval in the notebook (in seconds).
452 452
453 453 The default value is 120, or two minutes.
454 454 ``%autosave 0`` will disable autosave.
455 455
456 456 This magic only has an effect when called from the notebook interface.
457 457 It has no effect when called in a startup file.
458 458 """
459 459
460 460 try:
461 461 interval = int(arg_s)
462 462 except ValueError:
463 463 raise UsageError("%%autosave requires an integer, got %r" % arg_s)
464 464
465 465 # javascript wants milliseconds
466 466 milliseconds = 1000 * interval
467 467 display(Javascript("IPython.notebook.set_autosave_interval(%i)" % milliseconds),
468 468 include=['application/javascript']
469 469 )
470 470 if interval:
471 471 print("Autosaving every %i seconds" % interval)
472 472 else:
473 473 print("Autosave disabled")
474 474
475 475
476 476 class ZMQInteractiveShell(InteractiveShell):
477 477 """A subclass of InteractiveShell for ZMQ."""
478 478
479 479 displayhook_class = Type(ZMQShellDisplayHook)
480 480 display_pub_class = Type(ZMQDisplayPublisher)
481 481 data_pub_class = Type(ZMQDataPublisher)
482 482
483 483 # Override the traitlet in the parent class, because there's no point using
484 484 # readline for the kernel. Can be removed when the readline code is moved
485 485 # to the terminal frontend.
486 486 colors_force = CBool(True)
487 487 readline_use = CBool(False)
488 488 # autoindent has no meaning in a zmqshell, and attempting to enable it
489 489 # will print a warning in the absence of readline.
490 490 autoindent = CBool(False)
491 491
492 492 exiter = Instance(ZMQExitAutocall)
493 493 def _exiter_default(self):
494 494 return ZMQExitAutocall(self)
495 495
496 496 def _exit_now_changed(self, name, old, new):
497 497 """stop eventloop when exit_now fires"""
498 498 if new:
499 499 loop = ioloop.IOLoop.instance()
500 500 loop.add_timeout(time.time()+0.1, loop.stop)
501 501
502 502 keepkernel_on_exit = None
503 503
504 504 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
505 505 # interactive input being read; we provide event loop support in ipkernel
506 506 @staticmethod
507 507 def enable_gui(gui):
508 508 from .eventloops import enable_gui as real_enable_gui
509 509 try:
510 510 real_enable_gui(gui)
511 511 except ValueError as e:
512 512 raise UsageError("%s" % e)
513 513
514 514 def init_environment(self):
515 515 """Configure the user's environment.
516 516
517 517 """
518 518 env = os.environ
519 519 # These two ensure 'ls' produces nice coloring on BSD-derived systems
520 520 env['TERM'] = 'xterm-color'
521 521 env['CLICOLOR'] = '1'
522 522 # Since normal pagers don't work at all (over pexpect we don't have
523 523 # single-key control of the subprocess), try to disable paging in
524 524 # subprocesses as much as possible.
525 525 env['PAGER'] = 'cat'
526 526 env['GIT_PAGER'] = 'cat'
527 527
528 528 # And install the payload version of page.
529 529 install_payload_page()
530 530
531 531 def auto_rewrite_input(self, cmd):
532 532 """Called to show the auto-rewritten input for autocall and friends.
533 533
534 534 FIXME: this payload is currently not correctly processed by the
535 535 frontend.
536 536 """
537 537 new = self.prompt_manager.render('rewrite') + cmd
538 538 payload = dict(
539 source='IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
539 source='auto_rewrite_input',
540 540 transformed_input=new,
541 541 )
542 542 self.payload_manager.write_payload(payload)
543 543
544 544 def ask_exit(self):
545 545 """Engage the exit actions."""
546 546 self.exit_now = True
547 547 payload = dict(
548 source='IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
548 source='ask_exit',
549 549 exit=True,
550 550 keepkernel=self.keepkernel_on_exit,
551 551 )
552 552 self.payload_manager.write_payload(payload)
553 553
554 554 def _showtraceback(self, etype, evalue, stb):
555 555
556 556 exc_content = {
557 557 u'traceback' : stb,
558 558 u'ename' : unicode(etype.__name__),
559 559 u'evalue' : py3compat.safe_unicode(evalue),
560 560 }
561 561
562 562 dh = self.displayhook
563 563 # Send exception info over pub socket for other clients than the caller
564 564 # to pick up
565 565 topic = None
566 566 if dh.topic:
567 567 topic = dh.topic.replace(b'pyout', b'pyerr')
568 568
569 569 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
570 570
571 571 # FIXME - Hack: store exception info in shell object. Right now, the
572 572 # caller is reading this info after the fact, we need to fix this logic
573 573 # to remove this hack. Even uglier, we need to store the error status
574 574 # here, because in the main loop, the logic that sets it is being
575 575 # skipped because runlines swallows the exceptions.
576 576 exc_content[u'status'] = u'error'
577 577 self._reply_content = exc_content
578 578 # /FIXME
579 579
580 580 return exc_content
581 581
582 582 def set_next_input(self, text):
583 583 """Send the specified text to the frontend to be presented at the next
584 584 input cell."""
585 585 payload = dict(
586 source='IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
586 source='set_next_input',
587 587 text=text
588 588 )
589 589 self.payload_manager.write_payload(payload)
590 590
591 591 #-------------------------------------------------------------------------
592 592 # Things related to magics
593 593 #-------------------------------------------------------------------------
594 594
595 595 def init_magics(self):
596 596 super(ZMQInteractiveShell, self).init_magics()
597 597 self.register_magics(KernelMagics)
598 598 self.magics_manager.register_alias('ed', 'edit')
599 599
600 600
601 601
602 602 InteractiveShellABC.register(ZMQInteractiveShell)
@@ -1,584 +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 101 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
102 102 logical_line_transforms=[],
103 103 python_line_transforms=[],
104 104 )
105 105
106 106 # IPythonWidget protected class variables.
107 107 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
108 _payload_source_edit = zmq_shell_source + '.edit_magic'
109 _payload_source_exit = zmq_shell_source + '.ask_exit'
110 _payload_source_next_input = zmq_shell_source + '.set_next_input'
111 _payload_source_page = 'IPython.kernel.zmq.page.page'
108 _payload_source_edit = 'edit_magic'
109 _payload_source_exit = 'ask_exit'
110 _payload_source_next_input = 'set_next_input'
111 _payload_source_page = 'page'
112 112 _retrying_history_request = False
113 113
114 114 #---------------------------------------------------------------------------
115 115 # 'object' interface
116 116 #---------------------------------------------------------------------------
117 117
118 118 def __init__(self, *args, **kw):
119 119 super(IPythonWidget, self).__init__(*args, **kw)
120 120
121 121 # IPythonWidget protected variables.
122 122 self._payload_handlers = {
123 123 self._payload_source_edit : self._handle_payload_edit,
124 124 self._payload_source_exit : self._handle_payload_exit,
125 125 self._payload_source_page : self._handle_payload_page,
126 126 self._payload_source_next_input : self._handle_payload_next_input }
127 127 self._previous_prompt_obj = None
128 128 self._keep_kernel_on_exit = None
129 129
130 130 # Initialize widget styling.
131 131 if self.style_sheet:
132 132 self._style_sheet_changed()
133 133 self._syntax_style_changed()
134 134 else:
135 135 self.set_default_style()
136 136
137 137 #---------------------------------------------------------------------------
138 138 # 'BaseFrontendMixin' abstract interface
139 139 #---------------------------------------------------------------------------
140 140
141 141 def _handle_complete_reply(self, rep):
142 142 """ Reimplemented to support IPython's improved completion machinery.
143 143 """
144 144 self.log.debug("complete: %s", rep.get('content', ''))
145 145 cursor = self._get_cursor()
146 146 info = self._request_info.get('complete')
147 147 if info and info.id == rep['parent_header']['msg_id'] and \
148 148 info.pos == cursor.position():
149 149 matches = rep['content']['matches']
150 150 text = rep['content']['matched_text']
151 151 offset = len(text)
152 152
153 153 # Clean up matches with period and path separators if the matched
154 154 # text has not been transformed. This is done by truncating all
155 155 # but the last component and then suitably decreasing the offset
156 156 # between the current cursor position and the start of completion.
157 157 if len(matches) > 1 and matches[0][:offset] == text:
158 158 parts = re.split(r'[./\\]', text)
159 159 sep_count = len(parts) - 1
160 160 if sep_count:
161 161 chop_length = sum(map(len, parts[:sep_count])) + sep_count
162 162 matches = [ match[chop_length:] for match in matches ]
163 163 offset -= chop_length
164 164
165 165 # Move the cursor to the start of the match and complete.
166 166 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
167 167 self._complete_with_items(cursor, matches)
168 168
169 169 def _handle_execute_reply(self, msg):
170 170 """ Reimplemented to support prompt requests.
171 171 """
172 172 msg_id = msg['parent_header'].get('msg_id')
173 173 info = self._request_info['execute'].get(msg_id)
174 174 if info and info.kind == 'prompt':
175 175 number = msg['content']['execution_count'] + 1
176 176 self._show_interpreter_prompt(number)
177 177 self._request_info['execute'].pop(msg_id)
178 178 else:
179 179 super(IPythonWidget, self)._handle_execute_reply(msg)
180 180
181 181 def _handle_history_reply(self, msg):
182 182 """ Implemented to handle history tail replies, which are only supported
183 183 by the IPython kernel.
184 184 """
185 185 content = msg['content']
186 186 if 'history' not in content:
187 187 self.log.error("History request failed: %r"%content)
188 188 if content.get('status', '') == 'aborted' and \
189 189 not self._retrying_history_request:
190 190 # a *different* action caused this request to be aborted, so
191 191 # we should try again.
192 192 self.log.error("Retrying aborted history request")
193 193 # prevent multiple retries of aborted requests:
194 194 self._retrying_history_request = True
195 195 # wait out the kernel's queue flush, which is currently timed at 0.1s
196 196 time.sleep(0.25)
197 197 self.kernel_client.shell_channel.history(hist_access_type='tail',n=1000)
198 198 else:
199 199 self._retrying_history_request = False
200 200 return
201 201 # reset retry flag
202 202 self._retrying_history_request = False
203 203 history_items = content['history']
204 204 self.log.debug("Received history reply with %i entries", len(history_items))
205 205 items = []
206 206 last_cell = u""
207 207 for _, _, cell in history_items:
208 208 cell = cell.rstrip()
209 209 if cell != last_cell:
210 210 items.append(cell)
211 211 last_cell = cell
212 212 self._set_history(items)
213 213
214 214 def _handle_pyout(self, msg):
215 215 """ Reimplemented for IPython-style "display hook".
216 216 """
217 217 self.log.debug("pyout: %s", msg.get('content', ''))
218 218 if not self._hidden and self._is_from_this_session(msg):
219 219 content = msg['content']
220 220 prompt_number = content.get('execution_count', 0)
221 221 data = content['data']
222 222 if 'text/html' in data:
223 223 self._append_plain_text(self.output_sep, True)
224 224 self._append_html(self._make_out_prompt(prompt_number), True)
225 225 html = data['text/html']
226 226 self._append_plain_text('\n', True)
227 227 self._append_html(html + self.output_sep2, True)
228 228 elif 'text/plain' in data:
229 229 self._append_plain_text(self.output_sep, True)
230 230 self._append_html(self._make_out_prompt(prompt_number), True)
231 231 text = data['text/plain']
232 232 # If the repr is multiline, make sure we start on a new line,
233 233 # so that its lines are aligned.
234 234 if "\n" in text and not self.output_sep.endswith("\n"):
235 235 self._append_plain_text('\n', True)
236 236 self._append_plain_text(text + self.output_sep2, True)
237 237
238 238 def _handle_display_data(self, msg):
239 239 """ The base handler for the ``display_data`` message.
240 240 """
241 241 self.log.debug("display: %s", msg.get('content', ''))
242 242 # For now, we don't display data from other frontends, but we
243 243 # eventually will as this allows all frontends to monitor the display
244 244 # data. But we need to figure out how to handle this in the GUI.
245 245 if not self._hidden and self._is_from_this_session(msg):
246 246 source = msg['content']['source']
247 247 data = msg['content']['data']
248 248 metadata = msg['content']['metadata']
249 249 # In the regular IPythonWidget, we simply print the plain text
250 250 # representation.
251 251 if 'text/html' in data:
252 252 html = data['text/html']
253 253 self._append_html(html, True)
254 254 elif 'text/plain' in data:
255 255 text = data['text/plain']
256 256 self._append_plain_text(text, True)
257 257 # This newline seems to be needed for text and html output.
258 258 self._append_plain_text(u'\n', True)
259 259
260 260 def _started_channels(self):
261 261 """Reimplemented to make a history request and load %guiref."""
262 262 super(IPythonWidget, self)._started_channels()
263 263 self._load_guiref_magic()
264 264 self.kernel_client.shell_channel.history(hist_access_type='tail',
265 265 n=1000)
266 266
267 267 def _started_kernel(self):
268 268 """Load %guiref when the kernel starts (if channels are also started).
269 269
270 270 Principally triggered by kernel restart.
271 271 """
272 272 if self.kernel_client.shell_channel is not None:
273 273 self._load_guiref_magic()
274 274
275 275 def _load_guiref_magic(self):
276 276 """Load %guiref magic."""
277 277 self.kernel_client.shell_channel.execute('\n'.join([
278 278 "try:",
279 279 " _usage",
280 280 "except:",
281 281 " from IPython.core import usage as _usage",
282 282 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
283 283 " del _usage",
284 284 ]), silent=True)
285 285
286 286 #---------------------------------------------------------------------------
287 287 # 'ConsoleWidget' public interface
288 288 #---------------------------------------------------------------------------
289 289
290 290 #---------------------------------------------------------------------------
291 291 # 'FrontendWidget' public interface
292 292 #---------------------------------------------------------------------------
293 293
294 294 def execute_file(self, path, hidden=False):
295 295 """ Reimplemented to use the 'run' magic.
296 296 """
297 297 # Use forward slashes on Windows to avoid escaping each separator.
298 298 if sys.platform == 'win32':
299 299 path = os.path.normpath(path).replace('\\', '/')
300 300
301 301 # Perhaps we should not be using %run directly, but while we
302 302 # are, it is necessary to quote or escape filenames containing spaces
303 303 # or quotes.
304 304
305 305 # In earlier code here, to minimize escaping, we sometimes quoted the
306 306 # filename with single quotes. But to do this, this code must be
307 307 # platform-aware, because run uses shlex rather than python string
308 308 # parsing, so that:
309 309 # * In Win: single quotes can be used in the filename without quoting,
310 310 # and we cannot use single quotes to quote the filename.
311 311 # * In *nix: we can escape double quotes in a double quoted filename,
312 312 # but can't escape single quotes in a single quoted filename.
313 313
314 314 # So to keep this code non-platform-specific and simple, we now only
315 315 # use double quotes to quote filenames, and escape when needed:
316 316 if ' ' in path or "'" in path or '"' in path:
317 317 path = '"%s"' % path.replace('"', '\\"')
318 318 self.execute('%%run %s' % path, hidden=hidden)
319 319
320 320 #---------------------------------------------------------------------------
321 321 # 'FrontendWidget' protected interface
322 322 #---------------------------------------------------------------------------
323 323
324 324 def _complete(self):
325 325 """ Reimplemented to support IPython's improved completion machinery.
326 326 """
327 327 # We let the kernel split the input line, so we *always* send an empty
328 328 # text field. Readline-based frontends do get a real text field which
329 329 # they can use.
330 330 text = ''
331 331
332 332 # Send the completion request to the kernel
333 333 msg_id = self.kernel_client.shell_channel.complete(
334 334 text, # text
335 335 self._get_input_buffer_cursor_line(), # line
336 336 self._get_input_buffer_cursor_column(), # cursor_pos
337 337 self.input_buffer) # block
338 338 pos = self._get_cursor().position()
339 339 info = self._CompletionRequest(msg_id, pos)
340 340 self._request_info['complete'] = info
341 341
342 342 def _process_execute_error(self, msg):
343 343 """ Reimplemented for IPython-style traceback formatting.
344 344 """
345 345 content = msg['content']
346 346 traceback = '\n'.join(content['traceback']) + '\n'
347 347 if False:
348 348 # FIXME: For now, tracebacks come as plain text, so we can't use
349 349 # the html renderer yet. Once we refactor ultratb to produce
350 350 # properly styled tracebacks, this branch should be the default
351 351 traceback = traceback.replace(' ', '&nbsp;')
352 352 traceback = traceback.replace('\n', '<br/>')
353 353
354 354 ename = content['ename']
355 355 ename_styled = '<span class="error">%s</span>' % ename
356 356 traceback = traceback.replace(ename, ename_styled)
357 357
358 358 self._append_html(traceback)
359 359 else:
360 360 # This is the fallback for now, using plain text with ansi escapes
361 361 self._append_plain_text(traceback)
362 362
363 363 def _process_execute_payload(self, item):
364 364 """ Reimplemented to dispatch payloads to handler methods.
365 365 """
366 366 handler = self._payload_handlers.get(item['source'])
367 367 if handler is None:
368 368 # We have no handler for this type of payload, simply ignore it
369 369 return False
370 370 else:
371 371 handler(item)
372 372 return True
373 373
374 374 def _show_interpreter_prompt(self, number=None):
375 375 """ Reimplemented for IPython-style prompts.
376 376 """
377 377 # If a number was not specified, make a prompt number request.
378 378 if number is None:
379 379 msg_id = self.kernel_client.shell_channel.execute('', silent=True)
380 380 info = self._ExecutionRequest(msg_id, 'prompt')
381 381 self._request_info['execute'][msg_id] = info
382 382 return
383 383
384 384 # Show a new prompt and save information about it so that it can be
385 385 # updated later if the prompt number turns out to be wrong.
386 386 self._prompt_sep = self.input_sep
387 387 self._show_prompt(self._make_in_prompt(number), html=True)
388 388 block = self._control.document().lastBlock()
389 389 length = len(self._prompt)
390 390 self._previous_prompt_obj = self._PromptBlock(block, length, number)
391 391
392 392 # Update continuation prompt to reflect (possibly) new prompt length.
393 393 self._set_continuation_prompt(
394 394 self._make_continuation_prompt(self._prompt), html=True)
395 395
396 396 def _show_interpreter_prompt_for_reply(self, msg):
397 397 """ Reimplemented for IPython-style prompts.
398 398 """
399 399 # Update the old prompt number if necessary.
400 400 content = msg['content']
401 401 # abort replies do not have any keys:
402 402 if content['status'] == 'aborted':
403 403 if self._previous_prompt_obj:
404 404 previous_prompt_number = self._previous_prompt_obj.number
405 405 else:
406 406 previous_prompt_number = 0
407 407 else:
408 408 previous_prompt_number = content['execution_count']
409 409 if self._previous_prompt_obj and \
410 410 self._previous_prompt_obj.number != previous_prompt_number:
411 411 block = self._previous_prompt_obj.block
412 412
413 413 # Make sure the prompt block has not been erased.
414 414 if block.isValid() and block.text():
415 415
416 416 # Remove the old prompt and insert a new prompt.
417 417 cursor = QtGui.QTextCursor(block)
418 418 cursor.movePosition(QtGui.QTextCursor.Right,
419 419 QtGui.QTextCursor.KeepAnchor,
420 420 self._previous_prompt_obj.length)
421 421 prompt = self._make_in_prompt(previous_prompt_number)
422 422 self._prompt = self._insert_html_fetching_plain_text(
423 423 cursor, prompt)
424 424
425 425 # When the HTML is inserted, Qt blows away the syntax
426 426 # highlighting for the line, so we need to rehighlight it.
427 427 self._highlighter.rehighlightBlock(cursor.block())
428 428
429 429 self._previous_prompt_obj = None
430 430
431 431 # Show a new prompt with the kernel's estimated prompt number.
432 432 self._show_interpreter_prompt(previous_prompt_number + 1)
433 433
434 434 #---------------------------------------------------------------------------
435 435 # 'IPythonWidget' interface
436 436 #---------------------------------------------------------------------------
437 437
438 438 def set_default_style(self, colors='lightbg'):
439 439 """ Sets the widget style to the class defaults.
440 440
441 441 Parameters:
442 442 -----------
443 443 colors : str, optional (default lightbg)
444 444 Whether to use the default IPython light background or dark
445 445 background or B&W style.
446 446 """
447 447 colors = colors.lower()
448 448 if colors=='lightbg':
449 449 self.style_sheet = styles.default_light_style_sheet
450 450 self.syntax_style = styles.default_light_syntax_style
451 451 elif colors=='linux':
452 452 self.style_sheet = styles.default_dark_style_sheet
453 453 self.syntax_style = styles.default_dark_syntax_style
454 454 elif colors=='nocolor':
455 455 self.style_sheet = styles.default_bw_style_sheet
456 456 self.syntax_style = styles.default_bw_syntax_style
457 457 else:
458 458 raise KeyError("No such color scheme: %s"%colors)
459 459
460 460 #---------------------------------------------------------------------------
461 461 # 'IPythonWidget' protected interface
462 462 #---------------------------------------------------------------------------
463 463
464 464 def _edit(self, filename, line=None):
465 465 """ Opens a Python script for editing.
466 466
467 467 Parameters:
468 468 -----------
469 469 filename : str
470 470 A path to a local system file.
471 471
472 472 line : int, optional
473 473 A line of interest in the file.
474 474 """
475 475 if self.custom_edit:
476 476 self.custom_edit_requested.emit(filename, line)
477 477 elif not self.editor:
478 478 self._append_plain_text('No default editor available.\n'
479 479 'Specify a GUI text editor in the `IPythonWidget.editor` '
480 480 'configurable to enable the %edit magic')
481 481 else:
482 482 try:
483 483 filename = '"%s"' % filename
484 484 if line and self.editor_line:
485 485 command = self.editor_line.format(filename=filename,
486 486 line=line)
487 487 else:
488 488 try:
489 489 command = self.editor.format()
490 490 except KeyError:
491 491 command = self.editor.format(filename=filename)
492 492 else:
493 493 command += ' ' + filename
494 494 except KeyError:
495 495 self._append_plain_text('Invalid editor command.\n')
496 496 else:
497 497 try:
498 498 Popen(command, shell=True)
499 499 except OSError:
500 500 msg = 'Opening editor with command "%s" failed.\n'
501 501 self._append_plain_text(msg % command)
502 502
503 503 def _make_in_prompt(self, number):
504 504 """ Given a prompt number, returns an HTML In prompt.
505 505 """
506 506 try:
507 507 body = self.in_prompt % number
508 508 except TypeError:
509 509 # allow in_prompt to leave out number, e.g. '>>> '
510 510 body = self.in_prompt
511 511 return '<span class="in-prompt">%s</span>' % body
512 512
513 513 def _make_continuation_prompt(self, prompt):
514 514 """ Given a plain text version of an In prompt, returns an HTML
515 515 continuation prompt.
516 516 """
517 517 end_chars = '...: '
518 518 space_count = len(prompt.lstrip('\n')) - len(end_chars)
519 519 body = '&nbsp;' * space_count + end_chars
520 520 return '<span class="in-prompt">%s</span>' % body
521 521
522 522 def _make_out_prompt(self, number):
523 523 """ Given a prompt number, returns an HTML Out prompt.
524 524 """
525 525 body = self.out_prompt % number
526 526 return '<span class="out-prompt">%s</span>' % body
527 527
528 528 #------ Payload handlers --------------------------------------------------
529 529
530 530 # Payload handlers with a generic interface: each takes the opaque payload
531 531 # dict, unpacks it and calls the underlying functions with the necessary
532 532 # arguments.
533 533
534 534 def _handle_payload_edit(self, item):
535 535 self._edit(item['filename'], item['line_number'])
536 536
537 537 def _handle_payload_exit(self, item):
538 538 self._keep_kernel_on_exit = item['keepkernel']
539 539 self.exit_requested.emit(self)
540 540
541 541 def _handle_payload_next_input(self, item):
542 542 self.input_buffer = dedent(item['text'].rstrip())
543 543
544 544 def _handle_payload_page(self, item):
545 545 # Since the plain text widget supports only a very small subset of HTML
546 546 # and we have no control over the HTML source, we only page HTML
547 547 # payloads in the rich text widget.
548 548 if item['html'] and self.kind == 'rich':
549 549 self._page(item['html'], html=True)
550 550 else:
551 551 self._page(item['text'], html=False)
552 552
553 553 #------ Trait change handlers --------------------------------------------
554 554
555 555 def _style_sheet_changed(self):
556 556 """ Set the style sheets of the underlying widgets.
557 557 """
558 558 self.setStyleSheet(self.style_sheet)
559 559 if self._control is not None:
560 560 self._control.document().setDefaultStyleSheet(self.style_sheet)
561 561 bg_color = self._control.palette().window().color()
562 562 self._ansi_processor.set_background_color(bg_color)
563 563
564 564 if self._page_control is not None:
565 565 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
566 566
567 567
568 568
569 569 def _syntax_style_changed(self):
570 570 """ Set the style for the syntax highlighter.
571 571 """
572 572 if self._highlighter is None:
573 573 # ignore premature calls
574 574 return
575 575 if self.syntax_style:
576 576 self._highlighter.set_style(self.syntax_style)
577 577 else:
578 578 self._highlighter.set_style_sheet(self.style_sheet)
579 579
580 580 #------ Trait default initializers -----------------------------------------
581 581
582 582 def _banner_default(self):
583 583 from IPython.core.usage import default_gui_banner
584 584 return default_gui_banner
General Comments 0
You need to be logged in to leave comments. Login now