##// END OF EJS Templates
move Python-specific help links to kernel_info...
Min RK -
Show More
@@ -1,386 +1,425 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'jquery',
6 6 'base/js/namespace',
7 7 'base/js/dialog',
8 8 'base/js/utils',
9 9 'notebook/js/tour',
10 10 'bootstrap',
11 11 'moment',
12 12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
13 13 "use strict";
14 14
15 15 var MenuBar = function (selector, options) {
16 16 /**
17 17 * Constructor
18 18 *
19 19 * A MenuBar Class to generate the menubar of IPython notebook
20 20 *
21 21 * Parameters:
22 22 * selector: string
23 23 * options: dictionary
24 24 * Dictionary of keyword arguments.
25 25 * notebook: Notebook instance
26 26 * contents: ContentManager instance
27 27 * events: $(Events) instance
28 28 * save_widget: SaveWidget instance
29 29 * quick_help: QuickHelp instance
30 30 * base_url : string
31 31 * notebook_path : string
32 32 * notebook_name : string
33 33 */
34 34 options = options || {};
35 35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
36 36 this.selector = selector;
37 37 this.notebook = options.notebook;
38 38 this.contents = options.contents;
39 39 this.events = options.events;
40 40 this.save_widget = options.save_widget;
41 41 this.quick_help = options.quick_help;
42 42
43 43 try {
44 44 this.tour = new tour.Tour(this.notebook, this.events);
45 45 } catch (e) {
46 46 this.tour = undefined;
47 47 console.log("Failed to instantiate Notebook Tour", e);
48 48 }
49 49
50 50 if (this.selector !== undefined) {
51 51 this.element = $(selector);
52 52 this.style();
53 53 this.bind_events();
54 54 }
55 55 };
56 56
57 57 // TODO: This has definitively nothing to do with style ...
58 58 MenuBar.prototype.style = function () {
59 59 var that = this;
60 60 this.element.find("li").click(function (event, ui) {
61 61 // The selected cell loses focus when the menu is entered, so we
62 62 // re-select it upon selection.
63 63 var i = that.notebook.get_selected_index();
64 64 that.notebook.select(i);
65 65 }
66 66 );
67 67 };
68 68
69 69 MenuBar.prototype._nbconvert = function (format, download) {
70 70 download = download || false;
71 71 var notebook_path = this.notebook.notebook_path;
72 72 var url = utils.url_join_encode(
73 73 this.base_url,
74 74 'nbconvert',
75 75 format,
76 76 notebook_path
77 77 ) + "?download=" + download.toString();
78 78
79 var w = window.open()
79 var w = window.open();
80 80 if (this.notebook.dirty) {
81 81 this.notebook.save_notebook().then(function() {
82 82 w.location = url;
83 83 });
84 84 } else {
85 85 w.location = url;
86 86 }
87 87 };
88 88
89 89 MenuBar.prototype._size_header = function() {
90 90 /**
91 91 * Update header spacer size.
92 92 */
93 93 this.events.trigger('resize-header.Page');
94 94 };
95 95
96 96 MenuBar.prototype.bind_events = function () {
97 97 /**
98 98 * File
99 99 */
100 100 var that = this;
101 101 this.element.find('#new_notebook').click(function () {
102 102 var w = window.open();
103 103 // Create a new notebook in the same path as the current
104 104 // notebook's path.
105 105 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
106 106 that.contents.new_untitled(parent, {type: "notebook"}).then(
107 107 function (data) {
108 108 w.location = utils.url_join_encode(
109 109 that.base_url, 'notebooks', data.path
110 110 );
111 111 },
112 112 function(error) {
113 113 w.close();
114 114 dialog.modal({
115 115 title : 'Creating Notebook Failed',
116 116 body : "The error was: " + error.message,
117 117 buttons : {'OK' : {'class' : 'btn-primary'}}
118 118 });
119 119 }
120 120 );
121 121 });
122 122 this.element.find('#open_notebook').click(function () {
123 123 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
124 124 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
125 125 });
126 126 this.element.find('#copy_notebook').click(function () {
127 127 that.notebook.copy_notebook();
128 128 return false;
129 129 });
130 130 this.element.find('#download_ipynb').click(function () {
131 131 var base_url = that.notebook.base_url;
132 132 var notebook_path = that.notebook.notebook_path;
133 133 if (that.notebook.dirty) {
134 134 that.notebook.save_notebook({async : false});
135 135 }
136 136
137 137 var url = utils.url_join_encode(base_url, 'files', notebook_path);
138 138 window.open(url + '?download=1');
139 139 });
140 140
141 141 this.element.find('#print_preview').click(function () {
142 142 that._nbconvert('html', false);
143 143 });
144 144
145 145 this.element.find('#download_html').click(function () {
146 146 that._nbconvert('html', true);
147 147 });
148 148
149 149 this.element.find('#download_rst').click(function () {
150 150 that._nbconvert('rst', true);
151 151 });
152 152
153 153 this.element.find('#download_pdf').click(function () {
154 154 that._nbconvert('pdf', true);
155 155 });
156 156
157 157 this.element.find('#download_script').click(function () {
158 158 that._nbconvert('script', true);
159 159 });
160 160
161 161 this.element.find('#rename_notebook').click(function () {
162 162 that.save_widget.rename_notebook({notebook: that.notebook});
163 163 });
164 164 this.element.find('#save_checkpoint').click(function () {
165 165 that.notebook.save_checkpoint();
166 166 });
167 167 this.element.find('#restore_checkpoint').click(function () {
168 168 });
169 169 this.element.find('#trust_notebook').click(function () {
170 170 that.notebook.trust_notebook();
171 171 });
172 172 this.events.on('trust_changed.Notebook', function (event, trusted) {
173 173 if (trusted) {
174 174 that.element.find('#trust_notebook')
175 175 .addClass("disabled")
176 176 .find("a").text("Trusted Notebook");
177 177 } else {
178 178 that.element.find('#trust_notebook')
179 179 .removeClass("disabled")
180 180 .find("a").text("Trust Notebook");
181 181 }
182 182 });
183 183 this.element.find('#kill_and_exit').click(function () {
184 184 var close_window = function () {
185 185 /**
186 186 * allow closing of new tabs in Chromium, impossible in FF
187 187 */
188 188 window.open('', '_self', '');
189 189 window.close();
190 190 };
191 191 // finish with close on success or failure
192 192 that.notebook.session.delete(close_window, close_window);
193 193 });
194 194 // Edit
195 195 this.element.find('#cut_cell').click(function () {
196 196 that.notebook.cut_cell();
197 197 });
198 198 this.element.find('#copy_cell').click(function () {
199 199 that.notebook.copy_cell();
200 200 });
201 201 this.element.find('#delete_cell').click(function () {
202 202 that.notebook.delete_cell();
203 203 });
204 204 this.element.find('#undelete_cell').click(function () {
205 205 that.notebook.undelete_cell();
206 206 });
207 207 this.element.find('#split_cell').click(function () {
208 208 that.notebook.split_cell();
209 209 });
210 210 this.element.find('#merge_cell_above').click(function () {
211 211 that.notebook.merge_cell_above();
212 212 });
213 213 this.element.find('#merge_cell_below').click(function () {
214 214 that.notebook.merge_cell_below();
215 215 });
216 216 this.element.find('#move_cell_up').click(function () {
217 217 that.notebook.move_cell_up();
218 218 });
219 219 this.element.find('#move_cell_down').click(function () {
220 220 that.notebook.move_cell_down();
221 221 });
222 222 this.element.find('#edit_nb_metadata').click(function () {
223 223 that.notebook.edit_metadata({
224 224 notebook: that.notebook,
225 225 keyboard_manager: that.notebook.keyboard_manager});
226 226 });
227 227
228 228 // View
229 229 this.element.find('#toggle_header').click(function () {
230 230 $('div#header-container').toggle();
231 231 that._size_header();
232 232 });
233 233 this.element.find('#toggle_toolbar').click(function () {
234 234 $('div#maintoolbar').toggle();
235 235 that._size_header();
236 236 });
237 237 // Insert
238 238 this.element.find('#insert_cell_above').click(function () {
239 239 that.notebook.insert_cell_above('code');
240 240 that.notebook.select_prev();
241 241 });
242 242 this.element.find('#insert_cell_below').click(function () {
243 243 that.notebook.insert_cell_below('code');
244 244 that.notebook.select_next();
245 245 });
246 246 // Cell
247 247 this.element.find('#run_cell').click(function () {
248 248 that.notebook.execute_cell();
249 249 });
250 250 this.element.find('#run_cell_select_below').click(function () {
251 251 that.notebook.execute_cell_and_select_below();
252 252 });
253 253 this.element.find('#run_cell_insert_below').click(function () {
254 254 that.notebook.execute_cell_and_insert_below();
255 255 });
256 256 this.element.find('#run_all_cells').click(function () {
257 257 that.notebook.execute_all_cells();
258 258 });
259 259 this.element.find('#run_all_cells_above').click(function () {
260 260 that.notebook.execute_cells_above();
261 261 });
262 262 this.element.find('#run_all_cells_below').click(function () {
263 263 that.notebook.execute_cells_below();
264 264 });
265 265 this.element.find('#to_code').click(function () {
266 266 that.notebook.to_code();
267 267 });
268 268 this.element.find('#to_markdown').click(function () {
269 269 that.notebook.to_markdown();
270 270 });
271 271 this.element.find('#to_raw').click(function () {
272 272 that.notebook.to_raw();
273 273 });
274 274
275 275 this.element.find('#toggle_current_output').click(function () {
276 276 that.notebook.toggle_output();
277 277 });
278 278 this.element.find('#toggle_current_output_scroll').click(function () {
279 279 that.notebook.toggle_output_scroll();
280 280 });
281 281 this.element.find('#clear_current_output').click(function () {
282 282 that.notebook.clear_output();
283 283 });
284 284
285 285 this.element.find('#toggle_all_output').click(function () {
286 286 that.notebook.toggle_all_output();
287 287 });
288 288 this.element.find('#toggle_all_output_scroll').click(function () {
289 289 that.notebook.toggle_all_output_scroll();
290 290 });
291 291 this.element.find('#clear_all_output').click(function () {
292 292 that.notebook.clear_all_output();
293 293 });
294 294
295 295 // Kernel
296 296 this.element.find('#int_kernel').click(function () {
297 297 that.notebook.kernel.interrupt();
298 298 });
299 299 this.element.find('#restart_kernel').click(function () {
300 300 that.notebook.restart_kernel();
301 301 });
302 302 this.element.find('#reconnect_kernel').click(function () {
303 303 that.notebook.kernel.reconnect();
304 304 });
305 305 // Help
306 306 if (this.tour) {
307 307 this.element.find('#notebook_tour').click(function () {
308 308 that.tour.start();
309 309 });
310 310 } else {
311 311 this.element.find('#notebook_tour').addClass("disabled");
312 312 }
313 313 this.element.find('#keyboard_shortcuts').click(function () {
314 314 that.quick_help.show_keyboard_shortcuts();
315 315 });
316 316
317 317 this.update_restore_checkpoint(null);
318 318
319 319 this.events.on('checkpoints_listed.Notebook', function (event, data) {
320 320 that.update_restore_checkpoint(that.notebook.checkpoints);
321 321 });
322 322
323 323 this.events.on('checkpoint_created.Notebook', function (event, data) {
324 324 that.update_restore_checkpoint(that.notebook.checkpoints);
325 325 });
326 326
327 327 this.events.on('notebook_loaded.Notebook', function() {
328 328 var langinfo = that.notebook.metadata.language_info || {};
329 329 that.update_nbconvert_script(langinfo);
330 330 });
331 331
332 332 this.events.on('kernel_ready.Kernel', function(event, data) {
333 333 var langinfo = data.kernel.info_reply.language_info || {};
334 334 that.update_nbconvert_script(langinfo);
335 that.add_kernel_help_links(data.kernel.info_reply.help_links || []);
335 336 });
336 337 };
337 338
338 339 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
339 340 var ul = this.element.find("#restore_checkpoint").find("ul");
340 341 ul.empty();
341 342 if (!checkpoints || checkpoints.length === 0) {
342 343 ul.append(
343 344 $("<li/>")
344 345 .addClass("disabled")
345 346 .append(
346 347 $("<a/>")
347 348 .text("No checkpoints")
348 349 )
349 350 );
350 351 return;
351 352 }
352 353
353 354 var that = this;
354 355 checkpoints.map(function (checkpoint) {
355 356 var d = new Date(checkpoint.last_modified);
356 357 ul.append(
357 358 $("<li/>").append(
358 359 $("<a/>")
359 360 .attr("href", "#")
360 361 .text(moment(d).format("LLLL"))
361 362 .click(function () {
362 363 that.notebook.restore_checkpoint_dialog(checkpoint);
363 364 })
364 365 )
365 366 );
366 367 });
367 368 };
368 369
369 370 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
370 371 /**
371 372 * Set the 'Download as foo' menu option for the relevant language.
372 373 */
373 374 var el = this.element.find('#download_script');
374 375 var that = this;
375 376
376 377 // Set menu entry text to e.g. "Python (.py)"
377 var langname = (langinfo.name || 'Script')
378 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
378 var langname = (langinfo.name || 'Script');
379 langname = langname.charAt(0).toUpperCase()+langname.substr(1); // Capitalise
379 380 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
380 381 };
381 382
383 MenuBar.prototype.add_kernel_help_links = function(help_links) {
384 /** add links from kernel_info to the help menu */
385 var divider = $("#kernel-help-links");
386 if (divider.length === 0) {
387 // insert kernel help section above about link
388 var about = $("#notebook_about").parent();
389 divider = $("<li>")
390 .attr('id', "kernel-help-links")
391 .addClass('divider');
392 about.prev().before(divider);
393 }
394 // remove previous entries
395 while (!divider.next().hasClass('divider')) {
396 divider.next().remove();
397 }
398 if (help_links.length === 0) {
399 // no help links, remove the divider
400 divider.remove();
401 return;
402 }
403 var cursor = divider;
404 help_links.map(function (link) {
405 cursor.after($("<li>")
406 .append($("<a>")
407 .attr('target', '_blank')
408 .attr('title', 'Opens in a new window')
409 .attr('href', link.url)
410 .text(link.text)
411 .append($("<i>")
412 .addClass("fa fa-external-link menu-icon pull-right")
413 )
414 )
415 );
416 cursor = cursor.next();
417 });
418
419 };
420
382 421 // Backwards compatability.
383 422 IPython.MenuBar = MenuBar;
384 423
385 424 return {'MenuBar': MenuBar};
386 425 });
@@ -1,326 +1,318 b''
1 1 {% extends "page.html" %}
2 2
3 3 {% block stylesheet %}
4 4
5 5 {% if mathjax_url %}
6 6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 7 {% endif %}
8 8 <script type="text/javascript">
9 9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 10 // where it will be undefined, and should prompt a dialog later.
11 11 window.mathjax_url = "{{mathjax_url}}";
12 12 </script>
13 13
14 14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
15 15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
16 16
17 17 {{super()}}
18 18
19 19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
20 20 <link rel="stylesheet" href="" id='kernel-css' type="text/css" />
21 21
22 22 {% endblock %}
23 23
24 24 {% block params %}
25 25
26 26 data-project="{{project}}"
27 27 data-base-url="{{base_url}}"
28 28 data-ws-url="{{ws_url}}"
29 29 data-notebook-name="{{notebook_name}}"
30 30 data-notebook-path="{{notebook_path}}"
31 31 class="notebook_app"
32 32
33 33 {% endblock %}
34 34
35 35
36 36 {% block headercontainer %}
37 37
38 38
39 39 <span id="save_widget" class="pull-left save_widget">
40 40 <span class="filename"></span>
41 41 <span class="checkpoint_status"></span>
42 42 <span class="autosave_status"></span>
43 43 </span>
44 44
45 45 <span id="kernel_logo_widget">
46 46 <img class="current_kernel_logo" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"/>
47 47 </span>
48 48
49 49 {% endblock headercontainer %}
50 50
51 51 {% block header %}
52 52 <div id="menubar-container" class="container">
53 53 <div id="menubar">
54 54 <div id="menus" class="navbar navbar-default" role="navigation">
55 55 <div class="container-fluid">
56 56 <button type="button" class="btn btn-default navbar-btn navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
57 57 <i class="fa fa-bars"></i>
58 58 <span class="navbar-text">Menu</span>
59 59 </button>
60 60 <p id="kernel_indicator" class="navbar-text">
61 61 <span class="kernel_indicator_name">Kernel</span>
62 62 <i id="kernel_indicator_icon"></i>
63 63 </p>
64 64 <i id="modal_indicator" class="navbar-text"></i>
65 65 <span id="notification_area"></span>
66 66 <div class="navbar-collapse collapse">
67 67 <ul class="nav navbar-nav">
68 68 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
69 69 <ul id="file_menu" class="dropdown-menu">
70 70 <li id="new_notebook"
71 71 title="Make a new notebook (Opens a new window)">
72 72 <a href="#">New</a></li>
73 73 <li id="open_notebook"
74 74 title="Opens a new window with the Dashboard view">
75 75 <a href="#">Open...</a></li>
76 76 <!-- <hr/> -->
77 77 <li class="divider"></li>
78 78 <li id="copy_notebook"
79 79 title="Open a copy of this notebook's contents and start a new kernel">
80 80 <a href="#">Make a Copy...</a></li>
81 81 <li id="rename_notebook"><a href="#">Rename...</a></li>
82 82 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
83 83 <!-- <hr/> -->
84 84 <li class="divider"></li>
85 85 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
86 86 <ul class="dropdown-menu">
87 87 <li><a href="#"></a></li>
88 88 <li><a href="#"></a></li>
89 89 <li><a href="#"></a></li>
90 90 <li><a href="#"></a></li>
91 91 <li><a href="#"></a></li>
92 92 </ul>
93 93 </li>
94 94 <li class="divider"></li>
95 95 <li id="print_preview"><a href="#">Print Preview</a></li>
96 96 <li class="dropdown-submenu"><a href="#">Download as</a>
97 97 <ul class="dropdown-menu">
98 98 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
99 99 <li id="download_script"><a href="#">Script</a></li>
100 100 <li id="download_html"><a href="#">HTML (.html)</a></li>
101 101 <li id="download_rst"><a href="#">reST (.rst)</a></li>
102 102 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
103 103 </ul>
104 104 </li>
105 105 <li class="divider"></li>
106 106 <li id="trust_notebook"
107 107 title="Trust the output of this notebook">
108 108 <a href="#" >Trust Notebook</a></li>
109 109 <li class="divider"></li>
110 110 <li id="kill_and_exit"
111 111 title="Shutdown this notebook's kernel, and close this window">
112 112 <a href="#" >Close and halt</a></li>
113 113 </ul>
114 114 </li>
115 115 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
116 116 <ul id="edit_menu" class="dropdown-menu">
117 117 <li id="cut_cell"><a href="#">Cut Cell</a></li>
118 118 <li id="copy_cell"><a href="#">Copy Cell</a></li>
119 119 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
120 120 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
121 121 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
122 122 <li id="delete_cell"><a href="#">Delete Cell</a></li>
123 123 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
124 124 <li class="divider"></li>
125 125 <li id="split_cell"><a href="#">Split Cell</a></li>
126 126 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
127 127 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
128 128 <li class="divider"></li>
129 129 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
130 130 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
131 131 <li class="divider"></li>
132 132 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
133 133 </ul>
134 134 </li>
135 135 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
136 136 <ul id="view_menu" class="dropdown-menu">
137 137 <li id="toggle_header"
138 138 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
139 139 <a href="#">Toggle Header</a></li>
140 140 <li id="toggle_toolbar"
141 141 title="Show/Hide the action icons (below menu bar)">
142 142 <a href="#">Toggle Toolbar</a></li>
143 143 </ul>
144 144 </li>
145 145 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
146 146 <ul id="insert_menu" class="dropdown-menu">
147 147 <li id="insert_cell_above"
148 148 title="Insert an empty Code cell above the currently active cell">
149 149 <a href="#">Insert Cell Above</a></li>
150 150 <li id="insert_cell_below"
151 151 title="Insert an empty Code cell below the currently active cell">
152 152 <a href="#">Insert Cell Below</a></li>
153 153 </ul>
154 154 </li>
155 155 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
156 156 <ul id="cell_menu" class="dropdown-menu">
157 157 <li id="run_cell" title="Run this cell, and move cursor to the next one">
158 158 <a href="#">Run</a></li>
159 159 <li id="run_cell_select_below" title="Run this cell, select below">
160 160 <a href="#">Run and Select Below</a></li>
161 161 <li id="run_cell_insert_below" title="Run this cell, insert below">
162 162 <a href="#">Run and Insert Below</a></li>
163 163 <li id="run_all_cells" title="Run all cells in the notebook">
164 164 <a href="#">Run All</a></li>
165 165 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
166 166 <a href="#">Run All Above</a></li>
167 167 <li id="run_all_cells_below" title="Run this cell and all cells below it">
168 168 <a href="#">Run All Below</a></li>
169 169 <li class="divider"></li>
170 170 <li id="change_cell_type" class="dropdown-submenu"
171 171 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
172 172 <a href="#">Cell Type</a>
173 173 <ul class="dropdown-menu">
174 174 <li id="to_code"
175 175 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
176 176 <a href="#">Code</a></li>
177 177 <li id="to_markdown"
178 178 title="Contents will be rendered as HTML and serve as explanatory text">
179 179 <a href="#">Markdown</a></li>
180 180 <li id="to_raw"
181 181 title="Contents will pass through nbconvert unmodified">
182 182 <a href="#">Raw NBConvert</a></li>
183 183 </ul>
184 184 </li>
185 185 <li class="divider"></li>
186 186 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
187 187 <ul class="dropdown-menu">
188 188 <li id="toggle_current_output"
189 189 title="Hide/Show the output of the current cell">
190 190 <a href="#">Toggle</a>
191 191 </li>
192 192 <li id="toggle_current_output_scroll"
193 193 title="Scroll the output of the current cell">
194 194 <a href="#">Toggle Scrolling</a>
195 195 </li>
196 196 <li id="clear_current_output"
197 197 title="Clear the output of the current cell">
198 198 <a href="#">Clear</a>
199 199 </li>
200 200 </ul>
201 201 </li>
202 202 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
203 203 <ul class="dropdown-menu">
204 204 <li id="toggle_all_output"
205 205 title="Hide/Show the output of all cells">
206 206 <a href="#">Toggle</a>
207 207 </li>
208 208 <li id="toggle_all_output_scroll"
209 209 title="Scroll the output of all cells">
210 210 <a href="#">Toggle Scrolling</a>
211 211 </li>
212 212 <li id="clear_all_output"
213 213 title="Clear the output of all cells">
214 214 <a href="#">Clear</a>
215 215 </li>
216 216 </ul>
217 217 </li>
218 218 </ul>
219 219 </li>
220 220 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
221 221 <ul id="kernel_menu" class="dropdown-menu">
222 222 <li id="int_kernel"
223 223 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
224 224 <a href="#">Interrupt</a>
225 225 </li>
226 226 <li id="restart_kernel"
227 227 title="Restart the Kernel">
228 228 <a href="#">Restart</a>
229 229 </li>
230 230 <li id="reconnect_kernel"
231 231 title="Reconnect to the Kernel">
232 232 <a href="#">Reconnect</a>
233 233 </li>
234 234 <li class="divider"></li>
235 235 <li id="menu-change-kernel" class="dropdown-submenu">
236 236 <a href="#">Change kernel</a>
237 237 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
238 238 </li>
239 239 </ul>
240 240 </li>
241 241 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
242 242 <ul id="help_menu" class="dropdown-menu">
243 243 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
244 244 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
245 245 <li class="divider"></li>
246 246 {% set
247 247 sections = (
248 248 (
249 ("http://ipython.org/documentation.html","IPython Help",True),
250 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
251 ),(
252 ("http://docs.python.org","Python",True),
249 ("http://nbviewer.ipython.org/github/ipython/ipython/blob/2.x/examples/Index.ipynb", "Notebook Help", True),
253 250 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
254 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
255 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
256 ("http://matplotlib.org/contents.html","Matplotlib",True),
257 ("http://docs.sympy.org/latest/index.html","SymPy",True),
258 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
259 )
251 ),
260 252 )
261 253 %}
262 254
263 255 {% for helplinks in sections %}
264 256 {% for link in helplinks %}
265 257 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
266 258 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
267 259 {{link[1]}}
268 260 </a></li>
269 261 {% endfor %}
270 262 {% if not loop.last %}
271 263 <li class="divider"></li>
272 264 {% endif %}
273 265 {% endfor %}
274 266 <li class="divider"></li>
275 267 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
276 268 </ul>
277 269 </li>
278 270 </ul>
279 271 </div>
280 272 </div>
281 273 </div>
282 274 </div>
283 275
284 276 <div id="maintoolbar" class="navbar">
285 277 <div class="toolbar-inner navbar-inner navbar-nobg">
286 278 <div id="maintoolbar-container" class="container"></div>
287 279 </div>
288 280 </div>
289 281 </div>
290 282
291 283 <div class="lower-header-bar"></div>
292 284 {% endblock header %}
293 285
294 286 {% block site %}
295 287
296 288
297 289 <div id="ipython-main-app">
298 290 <div id="notebook_panel">
299 291 <div id="notebook"></div>
300 292 </div>
301 293 </div>
302 294
303 295 <div id="pager">
304 296 <div id="pager-contents">
305 297 <div id="pager-container" class="container"></div>
306 298 </div>
307 299 <div id='pager-button-area'></div>
308 300 </div>
309 301
310 302 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
311 303
312 304
313 305 {% endblock %}
314 306
315 307
316 308 {% block script %}
317 309 {{super()}}
318 310 <script type="text/javascript">
319 311 sys_info = {{sys_info}};
320 312 </script>
321 313
322 314 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
323 315
324 316 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
325 317
326 318 {% endblock %}
@@ -1,336 +1,367 b''
1 1 """The IPython kernel implementation"""
2 2
3 3 import getpass
4 4 import sys
5 5 import traceback
6 6
7 7 from IPython.core import release
8 8 from IPython.utils.py3compat import builtin_mod, PY3
9 9 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
10 from IPython.utils.traitlets import Instance, Type, Any
10 from IPython.utils.traitlets import Instance, Type, Any, List
11 11 from IPython.utils.decorators import undoc
12 12
13 13 from ..comm import CommManager
14 14 from .kernelbase import Kernel as KernelBase
15 15 from .serialize import serialize_object, unpack_apply_message
16 16 from .zmqshell import ZMQInteractiveShell
17 17
18 18
19 19 def lazy_import_handle_comm_opened(*args, **kwargs):
20 20 from IPython.html.widgets import Widget
21 21 Widget.handle_comm_opened(*args, **kwargs)
22 22
23 23
24 24 class IPythonKernel(KernelBase):
25 25 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
26 26 shell_class = Type(ZMQInteractiveShell)
27 27
28 28 user_module = Any()
29 29 def _user_module_changed(self, name, old, new):
30 30 if self.shell is not None:
31 31 self.shell.user_module = new
32 32
33 33 user_ns = Instance(dict, args=None, allow_none=True)
34 34 def _user_ns_changed(self, name, old, new):
35 35 if self.shell is not None:
36 36 self.shell.user_ns = new
37 37 self.shell.init_user_ns()
38 38
39 39 # A reference to the Python builtin 'raw_input' function.
40 40 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
41 41 _sys_raw_input = Any()
42 42 _sys_eval_input = Any()
43 43
44 44 def __init__(self, **kwargs):
45 45 super(IPythonKernel, self).__init__(**kwargs)
46 46
47 47 # Initialize the InteractiveShell subclass
48 48 self.shell = self.shell_class.instance(parent=self,
49 49 profile_dir = self.profile_dir,
50 50 user_module = self.user_module,
51 51 user_ns = self.user_ns,
52 52 kernel = self,
53 53 )
54 54 self.shell.displayhook.session = self.session
55 55 self.shell.displayhook.pub_socket = self.iopub_socket
56 56 self.shell.displayhook.topic = self._topic('execute_result')
57 57 self.shell.display_pub.session = self.session
58 58 self.shell.display_pub.pub_socket = self.iopub_socket
59 59 self.shell.data_pub.session = self.session
60 60 self.shell.data_pub.pub_socket = self.iopub_socket
61 61
62 62 # TMP - hack while developing
63 63 self.shell._reply_content = None
64 64
65 65 self.comm_manager = CommManager(shell=self.shell, parent=self,
66 66 kernel=self)
67 67 self.comm_manager.register_target('ipython.widget', lazy_import_handle_comm_opened)
68 68
69 69 self.shell.configurables.append(self.comm_manager)
70 70 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
71 71 for msg_type in comm_msg_types:
72 72 self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
73
74 help_links = List([
75 {
76 'text': "Python",
77 'url': "http://docs.python.org/%i.%i" % sys.version_info[:2],
78 },
79 {
80 'text': "IPython",
81 'url': "http://ipython.org/documentation.html",
82 },
83 {
84 'text': "NumPy",
85 'url': "http://docs.scipy.org/doc/numpy/reference/",
86 },
87 {
88 'text': "SciPy",
89 'url': "http://docs.scipy.org/doc/scipy/reference/",
90 },
91 {
92 'text': "Matplotlib",
93 'url': "http://matplotlib.org/contents.html",
94 },
95 {
96 'text': "SymPy",
97 'url': "http://docs.sympy.org/latest/index.html",
98 },
99 {
100 'text': "pandas",
101 'url': "http://pandas.pydata.org/pandas-docs/stable/",
102 },
103 ])
73 104
74 105 # Kernel info fields
75 106 implementation = 'ipython'
76 107 implementation_version = release.version
77 108 language_info = {
78 109 'name': 'python',
79 110 'version': sys.version.split()[0],
80 111 'mimetype': 'text/x-python',
81 112 'codemirror_mode': {'name': 'ipython',
82 113 'version': sys.version_info[0]},
83 114 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
84 115 'nbconvert_exporter': 'python',
85 116 'file_extension': '.py'
86 117 }
87 118 @property
88 119 def banner(self):
89 120 return self.shell.banner
90 121
91 122 def start(self):
92 123 self.shell.exit_now = False
93 124 super(IPythonKernel, self).start()
94 125
95 126 def set_parent(self, ident, parent):
96 127 """Overridden from parent to tell the display hook and output streams
97 128 about the parent message.
98 129 """
99 130 super(IPythonKernel, self).set_parent(ident, parent)
100 131 self.shell.set_parent(parent)
101 132
102 133 def _forward_input(self, allow_stdin=False):
103 134 """Forward raw_input and getpass to the current frontend.
104 135
105 136 via input_request
106 137 """
107 138 self._allow_stdin = allow_stdin
108 139
109 140 if PY3:
110 141 self._sys_raw_input = builtin_mod.input
111 142 builtin_mod.input = self.raw_input
112 143 else:
113 144 self._sys_raw_input = builtin_mod.raw_input
114 145 self._sys_eval_input = builtin_mod.input
115 146 builtin_mod.raw_input = self.raw_input
116 147 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
117 148 self._save_getpass = getpass.getpass
118 149 getpass.getpass = self.getpass
119 150
120 151 def _restore_input(self):
121 152 """Restore raw_input, getpass"""
122 153 if PY3:
123 154 builtin_mod.input = self._sys_raw_input
124 155 else:
125 156 builtin_mod.raw_input = self._sys_raw_input
126 157 builtin_mod.input = self._sys_eval_input
127 158
128 159 getpass.getpass = self._save_getpass
129 160
130 161 @property
131 162 def execution_count(self):
132 163 return self.shell.execution_count
133 164
134 165 @execution_count.setter
135 166 def execution_count(self, value):
136 167 # Ignore the incrememnting done by KernelBase, in favour of our shell's
137 168 # execution counter.
138 169 pass
139 170
140 171 def do_execute(self, code, silent, store_history=True,
141 172 user_expressions=None, allow_stdin=False):
142 173 shell = self.shell # we'll need this a lot here
143 174
144 175 self._forward_input(allow_stdin)
145 176
146 177 reply_content = {}
147 178 # FIXME: the shell calls the exception handler itself.
148 179 shell._reply_content = None
149 180 try:
150 181 shell.run_cell(code, store_history=store_history, silent=silent)
151 182 except:
152 183 status = u'error'
153 184 # FIXME: this code right now isn't being used yet by default,
154 185 # because the run_cell() call above directly fires off exception
155 186 # reporting. This code, therefore, is only active in the scenario
156 187 # where runlines itself has an unhandled exception. We need to
157 188 # uniformize this, for all exception construction to come from a
158 189 # single location in the codbase.
159 190 etype, evalue, tb = sys.exc_info()
160 191 tb_list = traceback.format_exception(etype, evalue, tb)
161 192 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
162 193 else:
163 194 status = u'ok'
164 195 finally:
165 196 self._restore_input()
166 197
167 198 reply_content[u'status'] = status
168 199
169 200 # Return the execution counter so clients can display prompts
170 201 reply_content['execution_count'] = shell.execution_count - 1
171 202
172 203 # FIXME - fish exception info out of shell, possibly left there by
173 204 # runlines. We'll need to clean up this logic later.
174 205 if shell._reply_content is not None:
175 206 reply_content.update(shell._reply_content)
176 207 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
177 208 reply_content['engine_info'] = e_info
178 209 # reset after use
179 210 shell._reply_content = None
180 211
181 212 if 'traceback' in reply_content:
182 213 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
183 214
184 215
185 216 # At this point, we can tell whether the main code execution succeeded
186 217 # or not. If it did, we proceed to evaluate user_expressions
187 218 if reply_content['status'] == 'ok':
188 219 reply_content[u'user_expressions'] = \
189 220 shell.user_expressions(user_expressions or {})
190 221 else:
191 222 # If there was an error, don't even try to compute expressions
192 223 reply_content[u'user_expressions'] = {}
193 224
194 225 # Payloads should be retrieved regardless of outcome, so we can both
195 226 # recover partial output (that could have been generated early in a
196 227 # block, before an error) and clear the payload system always.
197 228 reply_content[u'payload'] = shell.payload_manager.read_payload()
198 229 # Be agressive about clearing the payload because we don't want
199 230 # it to sit in memory until the next execute_request comes in.
200 231 shell.payload_manager.clear_payload()
201 232
202 233 return reply_content
203 234
204 235 def do_complete(self, code, cursor_pos):
205 236 # FIXME: IPython completers currently assume single line,
206 237 # but completion messages give multi-line context
207 238 # For now, extract line from cell, based on cursor_pos:
208 239 if cursor_pos is None:
209 240 cursor_pos = len(code)
210 241 line, offset = line_at_cursor(code, cursor_pos)
211 242 line_cursor = cursor_pos - offset
212 243
213 244 txt, matches = self.shell.complete('', line, line_cursor)
214 245 return {'matches' : matches,
215 246 'cursor_end' : cursor_pos,
216 247 'cursor_start' : cursor_pos - len(txt),
217 248 'metadata' : {},
218 249 'status' : 'ok'}
219 250
220 251 def do_inspect(self, code, cursor_pos, detail_level=0):
221 252 name = token_at_cursor(code, cursor_pos)
222 253 info = self.shell.object_inspect(name)
223 254
224 255 reply_content = {'status' : 'ok'}
225 256 reply_content['data'] = data = {}
226 257 reply_content['metadata'] = {}
227 258 reply_content['found'] = info['found']
228 259 if info['found']:
229 260 info_text = self.shell.object_inspect_text(
230 261 name,
231 262 detail_level=detail_level,
232 263 )
233 264 data['text/plain'] = info_text
234 265
235 266 return reply_content
236 267
237 268 def do_history(self, hist_access_type, output, raw, session=None, start=None,
238 269 stop=None, n=None, pattern=None, unique=False):
239 270 if hist_access_type == 'tail':
240 271 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
241 272 include_latest=True)
242 273
243 274 elif hist_access_type == 'range':
244 275 hist = self.shell.history_manager.get_range(session, start, stop,
245 276 raw=raw, output=output)
246 277
247 278 elif hist_access_type == 'search':
248 279 hist = self.shell.history_manager.search(
249 280 pattern, raw=raw, output=output, n=n, unique=unique)
250 281 else:
251 282 hist = []
252 283
253 284 return {'history' : list(hist)}
254 285
255 286 def do_shutdown(self, restart):
256 287 self.shell.exit_now = True
257 288 return dict(status='ok', restart=restart)
258 289
259 290 def do_is_complete(self, code):
260 291 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
261 292 r = {'status': status}
262 293 if status == 'incomplete':
263 294 r['indent'] = ' ' * indent_spaces
264 295 return r
265 296
266 297 def do_apply(self, content, bufs, msg_id, reply_metadata):
267 298 shell = self.shell
268 299 try:
269 300 working = shell.user_ns
270 301
271 302 prefix = "_"+str(msg_id).replace("-","")+"_"
272 303
273 304 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
274 305
275 306 fname = getattr(f, '__name__', 'f')
276 307
277 308 fname = prefix+"f"
278 309 argname = prefix+"args"
279 310 kwargname = prefix+"kwargs"
280 311 resultname = prefix+"result"
281 312
282 313 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
283 314 # print ns
284 315 working.update(ns)
285 316 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
286 317 try:
287 318 exec(code, shell.user_global_ns, shell.user_ns)
288 319 result = working.get(resultname)
289 320 finally:
290 321 for key in ns:
291 322 working.pop(key)
292 323
293 324 result_buf = serialize_object(result,
294 325 buffer_threshold=self.session.buffer_threshold,
295 326 item_threshold=self.session.item_threshold,
296 327 )
297 328
298 329 except:
299 330 # invoke IPython traceback formatting
300 331 shell.showtraceback()
301 332 # FIXME - fish exception info out of shell, possibly left there by
302 333 # run_code. We'll need to clean up this logic later.
303 334 reply_content = {}
304 335 if shell._reply_content is not None:
305 336 reply_content.update(shell._reply_content)
306 337 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
307 338 reply_content['engine_info'] = e_info
308 339 # reset after use
309 340 shell._reply_content = None
310 341
311 342 self.send_response(self.iopub_socket, u'error', reply_content,
312 343 ident=self._topic('error'))
313 344 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
314 345 result_buf = []
315 346
316 347 if reply_content['ename'] == 'UnmetDependency':
317 348 reply_metadata['dependencies_met'] = False
318 349 else:
319 350 reply_content = {'status' : 'ok'}
320 351
321 352 return reply_content, result_buf
322 353
323 354 def do_clear(self):
324 355 self.shell.reset(False)
325 356 return dict(status='ok')
326 357
327 358
328 359 # This exists only for backwards compatibility - use IPythonKernel instead
329 360
330 361 @undoc
331 362 class Kernel(IPythonKernel):
332 363 def __init__(self, *args, **kwargs):
333 364 import warnings
334 365 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
335 366 DeprecationWarning)
336 367 super(Kernel, self).__init__(*args, **kwargs) No newline at end of file
@@ -1,695 +1,699 b''
1 1 """Base class for a kernel that talks to frontends over 0MQ."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from __future__ import print_function
7 7
8 8 import sys
9 9 import time
10 10 import logging
11 11 import uuid
12 12
13 13 from datetime import datetime
14 14 from signal import (
15 15 signal, default_int_handler, SIGINT
16 16 )
17 17
18 18 import zmq
19 19 from zmq.eventloop import ioloop
20 20 from zmq.eventloop.zmqstream import ZMQStream
21 21
22 22 from IPython.config.configurable import SingletonConfigurable
23 23 from IPython.core.error import StdinNotImplementedError
24 24 from IPython.core import release
25 25 from IPython.utils import py3compat
26 26 from IPython.utils.py3compat import unicode_type, string_types
27 27 from IPython.utils.jsonutil import json_clean
28 28 from IPython.utils.traitlets import (
29 29 Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
30 30 )
31 31
32 32 from .session import Session
33 33
34 34
35 35 class Kernel(SingletonConfigurable):
36 36
37 37 #---------------------------------------------------------------------------
38 38 # Kernel interface
39 39 #---------------------------------------------------------------------------
40 40
41 41 # attribute to override with a GUI
42 42 eventloop = Any(None)
43 43 def _eventloop_changed(self, name, old, new):
44 44 """schedule call to eventloop from IOLoop"""
45 45 loop = ioloop.IOLoop.instance()
46 46 loop.add_callback(self.enter_eventloop)
47 47
48 48 session = Instance(Session)
49 49 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
50 50 shell_streams = List()
51 51 control_stream = Instance(ZMQStream)
52 52 iopub_socket = Instance(zmq.Socket)
53 53 stdin_socket = Instance(zmq.Socket)
54 54 log = Instance(logging.Logger)
55 55
56 56 # identities:
57 57 int_id = Integer(-1)
58 58 ident = Unicode()
59 59
60 60 def _ident_default(self):
61 61 return unicode_type(uuid.uuid4())
62 62
63 63 # This should be overridden by wrapper kernels that implement any real
64 64 # language.
65 65 language_info = {}
66
67 # any links that should go in the help menu
68 help_links = List()
66 69
67 70 # Private interface
68 71
69 72 _darwin_app_nap = Bool(True, config=True,
70 73 help="""Whether to use appnope for compatiblity with OS X App Nap.
71 74
72 75 Only affects OS X >= 10.9.
73 76 """
74 77 )
75 78
76 79 # track associations with current request
77 80 _allow_stdin = Bool(False)
78 81 _parent_header = Dict()
79 82 _parent_ident = Any(b'')
80 83 # Time to sleep after flushing the stdout/err buffers in each execute
81 84 # cycle. While this introduces a hard limit on the minimal latency of the
82 85 # execute cycle, it helps prevent output synchronization problems for
83 86 # clients.
84 87 # Units are in seconds. The minimum zmq latency on local host is probably
85 88 # ~150 microseconds, set this to 500us for now. We may need to increase it
86 89 # a little if it's not enough after more interactive testing.
87 90 _execute_sleep = Float(0.0005, config=True)
88 91
89 92 # Frequency of the kernel's event loop.
90 93 # Units are in seconds, kernel subclasses for GUI toolkits may need to
91 94 # adapt to milliseconds.
92 95 _poll_interval = Float(0.05, config=True)
93 96
94 97 # If the shutdown was requested over the network, we leave here the
95 98 # necessary reply message so it can be sent by our registered atexit
96 99 # handler. This ensures that the reply is only sent to clients truly at
97 100 # the end of our shutdown process (which happens after the underlying
98 101 # IPython shell's own shutdown).
99 102 _shutdown_message = None
100 103
101 104 # This is a dict of port number that the kernel is listening on. It is set
102 105 # by record_ports and used by connect_request.
103 106 _recorded_ports = Dict()
104 107
105 108 # set of aborted msg_ids
106 109 aborted = Set()
107 110
108 111 # Track execution count here. For IPython, we override this to use the
109 112 # execution count we store in the shell.
110 113 execution_count = 0
111 114
112 115
113 116 def __init__(self, **kwargs):
114 117 super(Kernel, self).__init__(**kwargs)
115 118
116 119 # Build dict of handlers for message types
117 120 msg_types = [ 'execute_request', 'complete_request',
118 121 'inspect_request', 'history_request',
119 122 'kernel_info_request',
120 123 'connect_request', 'shutdown_request',
121 124 'apply_request', 'is_complete_request',
122 125 ]
123 126 self.shell_handlers = {}
124 127 for msg_type in msg_types:
125 128 self.shell_handlers[msg_type] = getattr(self, msg_type)
126 129
127 130 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
128 131 self.control_handlers = {}
129 132 for msg_type in control_msg_types:
130 133 self.control_handlers[msg_type] = getattr(self, msg_type)
131 134
132 135
133 136 def dispatch_control(self, msg):
134 137 """dispatch control requests"""
135 138 idents,msg = self.session.feed_identities(msg, copy=False)
136 139 try:
137 140 msg = self.session.deserialize(msg, content=True, copy=False)
138 141 except:
139 142 self.log.error("Invalid Control Message", exc_info=True)
140 143 return
141 144
142 145 self.log.debug("Control received: %s", msg)
143 146
144 147 # Set the parent message for side effects.
145 148 self.set_parent(idents, msg)
146 149 self._publish_status(u'busy')
147 150
148 151 header = msg['header']
149 152 msg_type = header['msg_type']
150 153
151 154 handler = self.control_handlers.get(msg_type, None)
152 155 if handler is None:
153 156 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
154 157 else:
155 158 try:
156 159 handler(self.control_stream, idents, msg)
157 160 except Exception:
158 161 self.log.error("Exception in control handler:", exc_info=True)
159 162
160 163 sys.stdout.flush()
161 164 sys.stderr.flush()
162 165 self._publish_status(u'idle')
163 166
164 167 def dispatch_shell(self, stream, msg):
165 168 """dispatch shell requests"""
166 169 # flush control requests first
167 170 if self.control_stream:
168 171 self.control_stream.flush()
169 172
170 173 idents,msg = self.session.feed_identities(msg, copy=False)
171 174 try:
172 175 msg = self.session.deserialize(msg, content=True, copy=False)
173 176 except:
174 177 self.log.error("Invalid Message", exc_info=True)
175 178 return
176 179
177 180 # Set the parent message for side effects.
178 181 self.set_parent(idents, msg)
179 182 self._publish_status(u'busy')
180 183
181 184 header = msg['header']
182 185 msg_id = header['msg_id']
183 186 msg_type = msg['header']['msg_type']
184 187
185 188 # Print some info about this message and leave a '--->' marker, so it's
186 189 # easier to trace visually the message chain when debugging. Each
187 190 # handler prints its message at the end.
188 191 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
189 192 self.log.debug(' Content: %s\n --->\n ', msg['content'])
190 193
191 194 if msg_id in self.aborted:
192 195 self.aborted.remove(msg_id)
193 196 # is it safe to assume a msg_id will not be resubmitted?
194 197 reply_type = msg_type.split('_')[0] + '_reply'
195 198 status = {'status' : 'aborted'}
196 199 md = {'engine' : self.ident}
197 200 md.update(status)
198 201 self.session.send(stream, reply_type, metadata=md,
199 202 content=status, parent=msg, ident=idents)
200 203 return
201 204
202 205 handler = self.shell_handlers.get(msg_type, None)
203 206 if handler is None:
204 207 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
205 208 else:
206 209 # ensure default_int_handler during handler call
207 210 sig = signal(SIGINT, default_int_handler)
208 211 self.log.debug("%s: %s", msg_type, msg)
209 212 try:
210 213 handler(stream, idents, msg)
211 214 except Exception:
212 215 self.log.error("Exception in message handler:", exc_info=True)
213 216 finally:
214 217 signal(SIGINT, sig)
215 218
216 219 sys.stdout.flush()
217 220 sys.stderr.flush()
218 221 self._publish_status(u'idle')
219 222
220 223 def enter_eventloop(self):
221 224 """enter eventloop"""
222 225 self.log.info("entering eventloop %s", self.eventloop)
223 226 for stream in self.shell_streams:
224 227 # flush any pending replies,
225 228 # which may be skipped by entering the eventloop
226 229 stream.flush(zmq.POLLOUT)
227 230 # restore default_int_handler
228 231 signal(SIGINT, default_int_handler)
229 232 while self.eventloop is not None:
230 233 try:
231 234 self.eventloop(self)
232 235 except KeyboardInterrupt:
233 236 # Ctrl-C shouldn't crash the kernel
234 237 self.log.error("KeyboardInterrupt caught in kernel")
235 238 continue
236 239 else:
237 240 # eventloop exited cleanly, this means we should stop (right?)
238 241 self.eventloop = None
239 242 break
240 243 self.log.info("exiting eventloop")
241 244
242 245 def start(self):
243 246 """register dispatchers for streams"""
244 247 if self.control_stream:
245 248 self.control_stream.on_recv(self.dispatch_control, copy=False)
246 249
247 250 def make_dispatcher(stream):
248 251 def dispatcher(msg):
249 252 return self.dispatch_shell(stream, msg)
250 253 return dispatcher
251 254
252 255 for s in self.shell_streams:
253 256 s.on_recv(make_dispatcher(s), copy=False)
254 257
255 258 # publish idle status
256 259 self._publish_status('starting')
257 260
258 261 def do_one_iteration(self):
259 262 """step eventloop just once"""
260 263 if self.control_stream:
261 264 self.control_stream.flush()
262 265 for stream in self.shell_streams:
263 266 # handle at most one request per iteration
264 267 stream.flush(zmq.POLLIN, 1)
265 268 stream.flush(zmq.POLLOUT)
266 269
267 270
268 271 def record_ports(self, ports):
269 272 """Record the ports that this kernel is using.
270 273
271 274 The creator of the Kernel instance must call this methods if they
272 275 want the :meth:`connect_request` method to return the port numbers.
273 276 """
274 277 self._recorded_ports = ports
275 278
276 279 #---------------------------------------------------------------------------
277 280 # Kernel request handlers
278 281 #---------------------------------------------------------------------------
279 282
280 283 def _make_metadata(self, other=None):
281 284 """init metadata dict, for execute/apply_reply"""
282 285 new_md = {
283 286 'dependencies_met' : True,
284 287 'engine' : self.ident,
285 288 'started': datetime.now(),
286 289 }
287 290 if other:
288 291 new_md.update(other)
289 292 return new_md
290 293
291 294 def _publish_execute_input(self, code, parent, execution_count):
292 295 """Publish the code request on the iopub stream."""
293 296
294 297 self.session.send(self.iopub_socket, u'execute_input',
295 298 {u'code':code, u'execution_count': execution_count},
296 299 parent=parent, ident=self._topic('execute_input')
297 300 )
298 301
299 302 def _publish_status(self, status, parent=None):
300 303 """send status (busy/idle) on IOPub"""
301 304 self.session.send(self.iopub_socket,
302 305 u'status',
303 306 {u'execution_state': status},
304 307 parent=parent or self._parent_header,
305 308 ident=self._topic('status'),
306 309 )
307 310
308 311 def set_parent(self, ident, parent):
309 312 """Set the current parent_header
310 313
311 314 Side effects (IOPub messages) and replies are associated with
312 315 the request that caused them via the parent_header.
313 316
314 317 The parent identity is used to route input_request messages
315 318 on the stdin channel.
316 319 """
317 320 self._parent_ident = ident
318 321 self._parent_header = parent
319 322
320 323 def send_response(self, stream, msg_or_type, content=None, ident=None,
321 324 buffers=None, track=False, header=None, metadata=None):
322 325 """Send a response to the message we're currently processing.
323 326
324 327 This accepts all the parameters of :meth:`IPython.kernel.zmq.session.Session.send`
325 328 except ``parent``.
326 329
327 330 This relies on :meth:`set_parent` having been called for the current
328 331 message.
329 332 """
330 333 return self.session.send(stream, msg_or_type, content, self._parent_header,
331 334 ident, buffers, track, header, metadata)
332 335
333 336 def execute_request(self, stream, ident, parent):
334 337 """handle an execute_request"""
335 338
336 339 try:
337 340 content = parent[u'content']
338 341 code = py3compat.cast_unicode_py2(content[u'code'])
339 342 silent = content[u'silent']
340 343 store_history = content.get(u'store_history', not silent)
341 344 user_expressions = content.get('user_expressions', {})
342 345 allow_stdin = content.get('allow_stdin', False)
343 346 except:
344 347 self.log.error("Got bad msg: ")
345 348 self.log.error("%s", parent)
346 349 return
347 350
348 351 md = self._make_metadata(parent['metadata'])
349 352
350 353 # Re-broadcast our input for the benefit of listening clients, and
351 354 # start computing output
352 355 if not silent:
353 356 self.execution_count += 1
354 357 self._publish_execute_input(code, parent, self.execution_count)
355 358
356 359 reply_content = self.do_execute(code, silent, store_history,
357 360 user_expressions, allow_stdin)
358 361
359 362 # Flush output before sending the reply.
360 363 sys.stdout.flush()
361 364 sys.stderr.flush()
362 365 # FIXME: on rare occasions, the flush doesn't seem to make it to the
363 366 # clients... This seems to mitigate the problem, but we definitely need
364 367 # to better understand what's going on.
365 368 if self._execute_sleep:
366 369 time.sleep(self._execute_sleep)
367 370
368 371 # Send the reply.
369 372 reply_content = json_clean(reply_content)
370 373
371 374 md['status'] = reply_content['status']
372 375 if reply_content['status'] == 'error' and \
373 376 reply_content['ename'] == 'UnmetDependency':
374 377 md['dependencies_met'] = False
375 378
376 379 reply_msg = self.session.send(stream, u'execute_reply',
377 380 reply_content, parent, metadata=md,
378 381 ident=ident)
379 382
380 383 self.log.debug("%s", reply_msg)
381 384
382 385 if not silent and reply_msg['content']['status'] == u'error':
383 386 self._abort_queues()
384 387
385 388 def do_execute(self, code, silent, store_history=True,
386 389 user_experssions=None, allow_stdin=False):
387 390 """Execute user code. Must be overridden by subclasses.
388 391 """
389 392 raise NotImplementedError
390 393
391 394 def complete_request(self, stream, ident, parent):
392 395 content = parent['content']
393 396 code = content['code']
394 397 cursor_pos = content['cursor_pos']
395 398
396 399 matches = self.do_complete(code, cursor_pos)
397 400 matches = json_clean(matches)
398 401 completion_msg = self.session.send(stream, 'complete_reply',
399 402 matches, parent, ident)
400 403 self.log.debug("%s", completion_msg)
401 404
402 405 def do_complete(self, code, cursor_pos):
403 406 """Override in subclasses to find completions.
404 407 """
405 408 return {'matches' : [],
406 409 'cursor_end' : cursor_pos,
407 410 'cursor_start' : cursor_pos,
408 411 'metadata' : {},
409 412 'status' : 'ok'}
410 413
411 414 def inspect_request(self, stream, ident, parent):
412 415 content = parent['content']
413 416
414 417 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
415 418 content.get('detail_level', 0))
416 419 # Before we send this object over, we scrub it for JSON usage
417 420 reply_content = json_clean(reply_content)
418 421 msg = self.session.send(stream, 'inspect_reply',
419 422 reply_content, parent, ident)
420 423 self.log.debug("%s", msg)
421 424
422 425 def do_inspect(self, code, cursor_pos, detail_level=0):
423 426 """Override in subclasses to allow introspection.
424 427 """
425 428 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
426 429
427 430 def history_request(self, stream, ident, parent):
428 431 content = parent['content']
429 432
430 433 reply_content = self.do_history(**content)
431 434
432 435 reply_content = json_clean(reply_content)
433 436 msg = self.session.send(stream, 'history_reply',
434 437 reply_content, parent, ident)
435 438 self.log.debug("%s", msg)
436 439
437 440 def do_history(self, hist_access_type, output, raw, session=None, start=None,
438 441 stop=None, n=None, pattern=None, unique=False):
439 442 """Override in subclasses to access history.
440 443 """
441 444 return {'history': []}
442 445
443 446 def connect_request(self, stream, ident, parent):
444 447 if self._recorded_ports is not None:
445 448 content = self._recorded_ports.copy()
446 449 else:
447 450 content = {}
448 451 msg = self.session.send(stream, 'connect_reply',
449 452 content, parent, ident)
450 453 self.log.debug("%s", msg)
451 454
452 455 @property
453 456 def kernel_info(self):
454 457 return {
455 458 'protocol_version': release.kernel_protocol_version,
456 459 'implementation': self.implementation,
457 460 'implementation_version': self.implementation_version,
458 461 'language_info': self.language_info,
459 462 'banner': self.banner,
463 'help_links': self.help_links,
460 464 }
461 465
462 466 def kernel_info_request(self, stream, ident, parent):
463 467 msg = self.session.send(stream, 'kernel_info_reply',
464 468 self.kernel_info, parent, ident)
465 469 self.log.debug("%s", msg)
466 470
467 471 def shutdown_request(self, stream, ident, parent):
468 472 content = self.do_shutdown(parent['content']['restart'])
469 473 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
470 474 # same content, but different msg_id for broadcasting on IOPub
471 475 self._shutdown_message = self.session.msg(u'shutdown_reply',
472 476 content, parent
473 477 )
474 478
475 479 self._at_shutdown()
476 480 # call sys.exit after a short delay
477 481 loop = ioloop.IOLoop.instance()
478 482 loop.add_timeout(time.time()+0.1, loop.stop)
479 483
480 484 def do_shutdown(self, restart):
481 485 """Override in subclasses to do things when the frontend shuts down the
482 486 kernel.
483 487 """
484 488 return {'status': 'ok', 'restart': restart}
485 489
486 490 def is_complete_request(self, stream, ident, parent):
487 491 content = parent['content']
488 492 code = content['code']
489 493
490 494 reply_content = self.do_is_complete(code)
491 495 reply_content = json_clean(reply_content)
492 496 reply_msg = self.session.send(stream, 'is_complete_reply',
493 497 reply_content, parent, ident)
494 498 self.log.debug("%s", reply_msg)
495 499
496 500 def do_is_complete(self, code):
497 501 """Override in subclasses to find completions.
498 502 """
499 503 return {'status' : 'unknown',
500 504 }
501 505
502 506 #---------------------------------------------------------------------------
503 507 # Engine methods
504 508 #---------------------------------------------------------------------------
505 509
506 510 def apply_request(self, stream, ident, parent):
507 511 try:
508 512 content = parent[u'content']
509 513 bufs = parent[u'buffers']
510 514 msg_id = parent['header']['msg_id']
511 515 except:
512 516 self.log.error("Got bad msg: %s", parent, exc_info=True)
513 517 return
514 518
515 519 md = self._make_metadata(parent['metadata'])
516 520
517 521 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
518 522
519 523 # put 'ok'/'error' status in header, for scheduler introspection:
520 524 md['status'] = reply_content['status']
521 525
522 526 # flush i/o
523 527 sys.stdout.flush()
524 528 sys.stderr.flush()
525 529
526 530 self.session.send(stream, u'apply_reply', reply_content,
527 531 parent=parent, ident=ident,buffers=result_buf, metadata=md)
528 532
529 533 def do_apply(self, content, bufs, msg_id, reply_metadata):
530 534 """Override in subclasses to support the IPython parallel framework.
531 535 """
532 536 raise NotImplementedError
533 537
534 538 #---------------------------------------------------------------------------
535 539 # Control messages
536 540 #---------------------------------------------------------------------------
537 541
538 542 def abort_request(self, stream, ident, parent):
539 543 """abort a specific msg by id"""
540 544 msg_ids = parent['content'].get('msg_ids', None)
541 545 if isinstance(msg_ids, string_types):
542 546 msg_ids = [msg_ids]
543 547 if not msg_ids:
544 548 self._abort_queues()
545 549 for mid in msg_ids:
546 550 self.aborted.add(str(mid))
547 551
548 552 content = dict(status='ok')
549 553 reply_msg = self.session.send(stream, 'abort_reply', content=content,
550 554 parent=parent, ident=ident)
551 555 self.log.debug("%s", reply_msg)
552 556
553 557 def clear_request(self, stream, idents, parent):
554 558 """Clear our namespace."""
555 559 content = self.do_clear()
556 560 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
557 561 content = content)
558 562
559 563 def do_clear(self):
560 564 """Override in subclasses to clear the namespace
561 565
562 566 This is only required for IPython.parallel.
563 567 """
564 568 raise NotImplementedError
565 569
566 570 #---------------------------------------------------------------------------
567 571 # Protected interface
568 572 #---------------------------------------------------------------------------
569 573
570 574 def _topic(self, topic):
571 575 """prefixed topic for IOPub messages"""
572 576 if self.int_id >= 0:
573 577 base = "engine.%i" % self.int_id
574 578 else:
575 579 base = "kernel.%s" % self.ident
576 580
577 581 return py3compat.cast_bytes("%s.%s" % (base, topic))
578 582
579 583 def _abort_queues(self):
580 584 for stream in self.shell_streams:
581 585 if stream:
582 586 self._abort_queue(stream)
583 587
584 588 def _abort_queue(self, stream):
585 589 poller = zmq.Poller()
586 590 poller.register(stream.socket, zmq.POLLIN)
587 591 while True:
588 592 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
589 593 if msg is None:
590 594 return
591 595
592 596 self.log.info("Aborting:")
593 597 self.log.info("%s", msg)
594 598 msg_type = msg['header']['msg_type']
595 599 reply_type = msg_type.split('_')[0] + '_reply'
596 600
597 601 status = {'status' : 'aborted'}
598 602 md = {'engine' : self.ident}
599 603 md.update(status)
600 604 reply_msg = self.session.send(stream, reply_type, metadata=md,
601 605 content=status, parent=msg, ident=idents)
602 606 self.log.debug("%s", reply_msg)
603 607 # We need to wait a bit for requests to come in. This can probably
604 608 # be set shorter for true asynchronous clients.
605 609 poller.poll(50)
606 610
607 611
608 612 def _no_raw_input(self):
609 613 """Raise StdinNotImplentedError if active frontend doesn't support
610 614 stdin."""
611 615 raise StdinNotImplementedError("raw_input was called, but this "
612 616 "frontend does not support stdin.")
613 617
614 618 def getpass(self, prompt=''):
615 619 """Forward getpass to frontends
616 620
617 621 Raises
618 622 ------
619 623 StdinNotImplentedError if active frontend doesn't support stdin.
620 624 """
621 625 if not self._allow_stdin:
622 626 raise StdinNotImplementedError(
623 627 "getpass was called, but this frontend does not support input requests."
624 628 )
625 629 return self._input_request(prompt,
626 630 self._parent_ident,
627 631 self._parent_header,
628 632 password=True,
629 633 )
630 634
631 635 def raw_input(self, prompt=''):
632 636 """Forward raw_input to frontends
633 637
634 638 Raises
635 639 ------
636 640 StdinNotImplentedError if active frontend doesn't support stdin.
637 641 """
638 642 if not self._allow_stdin:
639 643 raise StdinNotImplementedError(
640 644 "raw_input was called, but this frontend does not support input requests."
641 645 )
642 646 return self._input_request(prompt,
643 647 self._parent_ident,
644 648 self._parent_header,
645 649 password=False,
646 650 )
647 651
648 652 def _input_request(self, prompt, ident, parent, password=False):
649 653 # Flush output before making the request.
650 654 sys.stderr.flush()
651 655 sys.stdout.flush()
652 656 # flush the stdin socket, to purge stale replies
653 657 while True:
654 658 try:
655 659 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
656 660 except zmq.ZMQError as e:
657 661 if e.errno == zmq.EAGAIN:
658 662 break
659 663 else:
660 664 raise
661 665
662 666 # Send the input request.
663 667 content = json_clean(dict(prompt=prompt, password=password))
664 668 self.session.send(self.stdin_socket, u'input_request', content, parent,
665 669 ident=ident)
666 670
667 671 # Await a response.
668 672 while True:
669 673 try:
670 674 ident, reply = self.session.recv(self.stdin_socket, 0)
671 675 except Exception:
672 676 self.log.warn("Invalid Message:", exc_info=True)
673 677 except KeyboardInterrupt:
674 678 # re-raise KeyboardInterrupt, to truncate traceback
675 679 raise KeyboardInterrupt
676 680 else:
677 681 break
678 682 try:
679 683 value = py3compat.unicode_to_str(reply['content']['value'])
680 684 except:
681 685 self.log.error("Bad input_reply: %s", parent)
682 686 value = ''
683 687 if value == '\x04':
684 688 # EOF
685 689 raise EOFError
686 690 return value
687 691
688 692 def _at_shutdown(self):
689 693 """Actions taken at shutdown by the kernel, called by python's atexit.
690 694 """
691 695 # io.rprint("Kernel at_shutdown") # dbg
692 696 if self._shutdown_message is not None:
693 697 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
694 698 self.log.debug("%s", self._shutdown_message)
695 699 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
General Comments 0
You need to be logged in to leave comments. Login now