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