##// END OF EJS Templates
Merge pull request #2421 from Carreau/nbconsole...
Brian E. Granger -
r8527:f851aea2 merge
parent child Browse files
Show More
@@ -1,408 +1,408 b''
1 1 /**
2 2 * Primary styles
3 3 *
4 4 * Author: IPython Development Team
5 5 */
6 6
7 7
8 8 body {
9 9 overflow: hidden;
10 10 }
11 11
12 12 span#save_widget {
13 13 padding: 5px;
14 14 margin: 0px 0px 0px 300px;
15 15 display:inline-block;
16 16 }
17 17
18 18 span#notebook_name {
19 19 height: 1em;
20 20 line-height: 1em;
21 21 padding: 3px;
22 22 border: none;
23 23 font-size: 146.5%;
24 24 }
25 25
26 26 .ui-menubar-item .ui-button .ui-button-text {
27 27 padding: 0.4em 1.0em;
28 28 font-size: 100%;
29 29 }
30 30
31 31 .ui-menu {
32 32 -moz-box-shadow: 0px 6px 10px -1px #adadad;
33 33 -webkit-box-shadow: 0px 6px 10px -1px #adadad;
34 34 box-shadow: 0px 6px 10px -1px #adadad;
35 35 }
36 36
37 37 .ui-menu .ui-menu-item a {
38 38 border: 1px solid transparent;
39 39 padding: 2px 1.6em;
40 40 }
41 41
42 42 .ui-menu .ui-menu-item a.ui-state-focus {
43 43 margin: 0;
44 44 }
45 45
46 46 .ui-menu hr {
47 47 margin: 0.3em 0;
48 48 }
49 49
50 50 #menubar_container {
51 51 position: relative;
52 52 }
53 53
54 54 #notification_area {
55 55 position: absolute;
56 56 right: 0px;
57 57 top: 0px;
58 58 height: 25px;
59 59 padding: 3px 0px;
60 60 padding-right: 3px;
61 61 z-index: 10;
62 62 }
63 63
64 64 .notification_widget{
65 65 float : right;
66 66 right: 0px;
67 67 top: 1px;
68 68 height: 25px;
69 69 padding: 3px 6px;
70 70 z-index: 10;
71 71 }
72 72
73 73 .toolbar {
74 74 padding: 3px 15px;
75 75 }
76 76
77 77 #cell_type {
78 78 font-size: 85%;
79 79 }
80 80
81 81
82 82 div#main_app {
83 83 width: 100%;
84 84 position: relative;
85 85 }
86 86
87 87 span#quick_help_area {
88 88 position: static;
89 89 padding: 5px 0px;
90 90 margin: 0px 0px 0px 0px;
91 91 }
92 92
93 93 .help_string {
94 94 float: right;
95 95 width: 170px;
96 96 padding: 0px 5px;
97 97 text-align: left;
98 98 font-size: 85%;
99 99 }
100 100
101 101 .help_string_label {
102 102 float: right;
103 103 font-size: 85%;
104 104 }
105 105
106 106 div#notebook_panel {
107 107 margin: 0px 0px 0px 0px;
108 108 padding: 0px;
109 109 }
110 110
111 111 div#notebook {
112 112 overflow-y: scroll;
113 113 overflow-x: auto;
114 114 width: 100%;
115 115 /* This spaces the cell away from the edge of the notebook area */
116 116 padding: 5px 5px 15px 5px;
117 117 margin: 0px;
118 118 background-color: white;
119 119 }
120 120
121 121 div#pager_splitter {
122 122 height: 8px;
123 123 }
124 124
125 125 #pager_container {
126 126 position : relative;
127 127 }
128 128
129 129 div#pager {
130 130 padding: 15px;
131 131 overflow: auto;
132 132 display: none;
133 133 }
134 134
135 135 div.ui-widget-content {
136 136 border: 1px solid #aaa;
137 137 outline: none;
138 138 }
139 139
140 140 .cell {
141 141 border: 1px solid transparent;
142 142 }
143 143
144 144 div.cell {
145 145 width: 100%;
146 146 padding: 5px 5px 5px 0px;
147 147 /* This acts as a spacer between cells, that is outside the border */
148 148 margin: 2px 0px 2px 0px;
149 149 }
150 150
151 151 div.code_cell {
152 152 background-color: white;
153 153 }
154 154
155 155 /* any special styling for code cells that are currently running goes here */
156 156 div.code_cell.running {
157 157 }
158 158
159 159 div.prompt {
160 160 /* This needs to be wide enough for 3 digit prompt numbers: In[100]: */
161 161 width: 11ex;
162 162 /* This 0.4em is tuned to match the padding on the CodeMirror editor. */
163 163 padding: 0.4em;
164 164 margin: 0px;
165 165 font-family: monospace;
166 166 text-align:right;
167 167 }
168 168
169 169 div.input {
170 170 page-break-inside: avoid;
171 171 }
172 172
173 173 /* input_area and input_prompt must match in top border and margin for alignment */
174 174 div.input_area {
175 175 color: black;
176 176 border: 1px solid #ddd;
177 177 border-radius: 3px;
178 178 background: #f7f7f7;
179 179 }
180 180
181 181 div.input_prompt {
182 182 color: navy;
183 183 border-top: 1px solid transparent;
184 184 }
185 185
186 186 div.output_wrapper {
187 187 /* This is a spacer between the input and output of each cell */
188 188 margin-top: 5px;
189 189 margin-left: 5px;
190 190 /* FF needs explicit width to stretch */
191 191 width: 100%;
192 192 /* this position must be relative to enable descendents to be absolute within it */
193 193 position: relative;
194 194 }
195 195
196 196 /* class for the output area when it should be height-limited */
197 197 div.output_scroll {
198 198 /* ideally, this would be max-height, but FF barfs all over that */
199 199 height: 24em;
200 200 /* FF needs this *and the wrapper* to specify full width, or it will shrinkwrap */
201 201 width: 100%;
202 202
203 203 overflow: auto;
204 204 border-radius: 3px;
205 205 box-shadow: inset 0 2px 8px rgba(0, 0, 0, .8);
206 206 }
207 207
208 208 /* output div while it is collapsed */
209 209 div.output_collapsed {
210 210 margin-right: 5px;
211 211 }
212 212
213 213 div.out_prompt_overlay {
214 214 height: 100%;
215 215 padding: 0px;
216 216 position: absolute;
217 217 border-radius: 3px;
218 218 }
219 219
220 220 div.out_prompt_overlay:hover {
221 221 /* use inner shadow to get border that is computed the same on WebKit/FF */
222 222 box-shadow: inset 0 0 1px #000;
223 223 background: rgba(240, 240, 240, 0.5);
224 224 }
225 225
226 226 div.output_prompt {
227 227 color: darkred;
228 228 /* 5px right shift to account for margin in parent container */
229 229 margin: 0 5px 0 -5px;
230 230 }
231 231
232 232 /* This class is the outer container of all output sections. */
233 233 div.output_area {
234 234 padding: 0px;
235 235 page-break-inside: avoid;
236 236 }
237 237
238 238 /* This class is for the output subarea inside the output_area and after
239 239 the prompt div. */
240 240 div.output_subarea {
241 padding: 0.4em 0.4em 0.4em 0.4em;
241 padding: 0.44em 0.4em 0.4em 1px;
242 242 }
243 243
244 244 /* The rest of the output_* classes are for special styling of the different
245 245 output types */
246 246
247 247 /* all text output has this class: */
248 248 div.output_text {
249 249 text-align: left;
250 250 color: black;
251 251 font-family: monospace;
252 252 }
253 253
254 254 /* stdout/stderr are 'text' as well as 'stream', but pyout/pyerr are *not* streams */
255 255 div.output_stream {
256 256 padding-top: 0.0em;
257 257 padding-bottom: 0.0em;
258 258 }
259 259 div.output_stdout {
260 260 }
261 261 div.output_stderr {
262 262 background: #fdd; /* very light red background for stderr */
263 263 }
264 264
265 265 div.output_latex {
266 266 text-align: left;
267 267 color: black;
268 268 }
269 269
270 270 div.output_html {
271 271 }
272 272
273 273 div.output_png {
274 274 }
275 275
276 276 div.output_jpeg {
277 277 }
278 278
279 279 div.text_cell {
280 280 background-color: white;
281 281 padding: 5px 5px 5px 5px;
282 282 }
283 283
284 284 div.text_cell_input {
285 285 color: black;
286 286 border: 1px solid #ddd;
287 287 border-radius: 3px;
288 288 background: #f7f7f7;
289 289 }
290 290
291 291 div.text_cell_render {
292 292 font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
293 293 outline: none;
294 294 resize: none;
295 295 width: inherit;
296 296 border-style: none;
297 297 padding: 5px;
298 298 color: black;
299 299 }
300 300
301 301 /* The following gets added to the <head> if it is detected that the user has a
302 302 * monospace font with inconsistent normal/bold/italic height. See
303 303 * notebookmain.js. Such fonts will have keywords vertically offset with
304 304 * respect to the rest of the text. The user should select a better font.
305 305 * See: https://github.com/ipython/ipython/issues/1503
306 306 *
307 307 * .CodeMirror span {
308 308 * vertical-align: bottom;
309 309 * }
310 310 */
311 311
312 312 .CodeMirror {
313 313 line-height: 1.231; /* Changed from 1em to our global default */
314 314 }
315 315
316 316 .CodeMirror-scroll {
317 317 height: auto; /* Changed to auto to autogrow */
318 318 /* The CodeMirror docs are a bit fuzzy on if overflow-y should be hidden or visible.*/
319 319 /* We have found that if it is visible, vertical scrollbars appear with font size changes.*/
320 320 overflow-y: hidden;
321 321 overflow-x: auto; /* Changed from auto to remove scrollbar */
322 322 }
323 323
324 324 /* CSS font colors for translated ANSI colors. */
325 325
326 326
327 327 .ansiblack {color: black;}
328 328 .ansired {color: darkred;}
329 329 .ansigreen {color: darkgreen;}
330 330 .ansiyellow {color: brown;}
331 331 .ansiblue {color: darkblue;}
332 332 .ansipurple {color: darkviolet;}
333 333 .ansicyan {color: steelblue;}
334 334 .ansigrey {color: grey;}
335 335 .ansibold {font-weight: bold;}
336 336
337 337 .completions {
338 338 position: absolute;
339 339 z-index: 10;
340 340 overflow: hidden;
341 341 border: 1px solid grey;
342 342 }
343 343
344 344 .completions select {
345 345 background: white;
346 346 outline: none;
347 347 border: none;
348 348 padding: 0px;
349 349 margin: 0px;
350 350 overflow: auto;
351 351 font-family: monospace;
352 352 }
353 353
354 354 option.context {
355 355 background-color: #DEF7FF;
356 356 }
357 357 option.introspection {
358 358 background-color: #EBF4EB;
359 359 }
360 360
361 361 /*fixed part of the completion*/
362 362 .completions p b {
363 363 font-weight:bold;
364 364 }
365 365
366 366 .completions p {
367 367 background: #DDF;
368 368 /*outline: none;
369 369 padding: 0px;*/
370 370 border-bottom: black solid 1px;
371 371 padding: 1px;
372 372 font-family: monospace;
373 373 }
374 374
375 375 pre.dialog {
376 376 background-color: #f7f7f7;
377 377 border: 1px solid #ddd;
378 378 border-radius: 3px;
379 379 padding: 0.4em;
380 380 padding-left: 2em;
381 381 }
382 382
383 383 p.dialog {
384 384 padding : 0.2em;
385 385 }
386 386
387 387 .shortcut_key {
388 388 display: inline-block;
389 389 width: 15ex;
390 390 text-align: right;
391 391 font-family: monospace;
392 392 }
393 393
394 394 .shortcut_descr {
395 395 }
396 396
397 397 /* Word-wrap output correctly. This is the CSS3 spelling, though Firefox seems
398 398 to not honor it correctly. Webkit browsers (Chrome, rekonq, Safari) do.
399 399 */
400 400 pre, code, kbd, samp { white-space: pre-wrap; }
401 401
402 402 #fonttest {
403 403 font-family: monospace;
404 404 }
405 405
406 406 .js-error {
407 407 color: darkred;
408 408 }
@@ -1,322 +1,345 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // CodeCell
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13 "use strict";
14 14
15 15 var utils = IPython.utils;
16 16 var key = IPython.utils.keycodes;
17 17 CodeMirror.modeURL = "/static/codemirror/mode/%N/%N.js";
18 18
19 19 var CodeCell = function (kernel) {
20 20 // The kernel doesn't have to be set at creation time, in that case
21 21 // it will be null and set_kernel has to be called later.
22 22 this.kernel = kernel || null;
23 23 this.code_mirror = null;
24 24 this.input_prompt_number = null;
25 25 this.tooltip_on_tab = true;
26 26 this.collapsed = false;
27 27 this.default_mode = 'python';
28 28 IPython.Cell.apply(this, arguments);
29 29
30 30 var that = this;
31 31 this.element.focusout(
32 32 function() { that.auto_highlight(); }
33 33 );
34 34 };
35 35
36 36
37 37 CodeCell.prototype = new IPython.Cell();
38 38
39 39
40 40 CodeCell.prototype.auto_highlight = function () {
41 41 this._auto_highlight(IPython.config.cell_magic_highlight)
42 42 };
43 43
44 44 CodeCell.prototype.create_element = function () {
45 45 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
46 46 cell.attr('tabindex','2');
47 47 var input = $('<div></div>').addClass('input hbox');
48 48 input.append($('<div/>').addClass('prompt input_prompt'));
49 49 var input_area = $('<div/>').addClass('input_area box-flex1');
50 50 this.code_mirror = CodeMirror(input_area.get(0), {
51 51 indentUnit : 4,
52 52 mode: 'python',
53 53 theme: 'ipython',
54 54 readOnly: this.read_only,
55 55 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess",'Backspace':"delSpaceToPrevTabStop"},
56 56 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
57 57 });
58 58 input.append(input_area);
59 59 var output = $('<div></div>');
60 60 cell.append(input).append(output);
61 61 this.element = cell;
62 62 this.output_area = new IPython.OutputArea(output, true);
63 63
64 64 // construct a completer only if class exist
65 65 // otherwise no print view
66 66 if (IPython.Completer !== undefined)
67 67 {
68 68 this.completer = new IPython.Completer(this);
69 69 }
70 70 };
71 71
72 72 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
73 73 // This method gets called in CodeMirror's onKeyDown/onKeyPress
74 74 // handlers and is used to provide custom key handling. Its return
75 75 // value is used to determine if CodeMirror should ignore the event:
76 76 // true = ignore, false = don't ignore.
77 77
78 78 if (this.read_only){
79 79 return false;
80 80 }
81 81
82 82 var that = this;
83 83 // whatever key is pressed, first, cancel the tooltip request before
84 84 // they are sent, and remove tooltip if any, except for tab again
85 85 if (event.type === 'keydown' && event.which != key.TAB ) {
86 86 IPython.tooltip.remove_and_cancel_tooltip();
87 87 };
88 88
89 89 var cur = editor.getCursor();
90 90 if (event.keyCode === key.ENTER){
91 91 this.auto_highlight();
92 92 }
93 93
94 94 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
95 95 // Always ignore shift-enter in CodeMirror as we handle it.
96 96 return true;
97 97 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
98 98 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
99 99 // browser and keyboard layout !
100 100 // Pressing '(' , request tooltip, don't forget to reappend it
101 101 IPython.tooltip.pending(that);
102 102 } else if (event.which === key.UPARROW && event.type === 'keydown') {
103 103 // If we are not at the top, let CM handle the up arrow and
104 104 // prevent the global keydown handler from handling it.
105 105 if (!that.at_top()) {
106 106 event.stop();
107 107 return false;
108 108 } else {
109 109 return true;
110 110 };
111 111 } else if (event.which === key.ESC) {
112 112 IPython.tooltip.remove_and_cancel_tooltip(true);
113 113 return true;
114 114 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
115 115 // If we are not at the bottom, let CM handle the down arrow and
116 116 // prevent the global keydown handler from handling it.
117 117 if (!that.at_bottom()) {
118 118 event.stop();
119 119 return false;
120 120 } else {
121 121 return true;
122 122 };
123 123 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
124 124 // Tab completion.
125 125 //Do not trim here because of tooltip
126 126 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
127 127 if (pre_cursor.trim() === "") {
128 128 // Don't autocomplete if the part of the line before the cursor
129 129 // is empty. In this case, let CodeMirror handle indentation.
130 130 return false;
131 131 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && that.tooltip_on_tab ) {
132 132 IPython.tooltip.request(that);
133 133 // Prevent the event from bubbling up.
134 134 event.stop();
135 135 // Prevent CodeMirror from handling the tab.
136 136 return true;
137 137 } else {
138 138 event.stop();
139 139 this.completer.startCompletion();
140 140 return true;
141 141 };
142 142 } else {
143 143 // keypress/keyup also trigger on TAB press, and we don't want to
144 144 // use those to disable tab completion.
145 145 return false;
146 146 };
147 147 return false;
148 148 };
149 149
150 150
151 151 // Kernel related calls.
152 152
153 153 CodeCell.prototype.set_kernel = function (kernel) {
154 154 this.kernel = kernel;
155 155 }
156 156
157 157
158 158 CodeCell.prototype.execute = function () {
159 159 this.output_area.clear_output(true, true, true);
160 160 this.set_input_prompt('*');
161 161 this.element.addClass("running");
162 162 var callbacks = {
163 163 'execute_reply': $.proxy(this._handle_execute_reply, this),
164 164 'output': $.proxy(this.output_area.handle_output, this.output_area),
165 165 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
166 166 'set_next_input': $.proxy(this._handle_set_next_input, this)
167 167 };
168 168 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
169 169 };
170 170
171 171
172 172 CodeCell.prototype._handle_execute_reply = function (content) {
173 173 this.set_input_prompt(content.execution_count);
174 174 this.element.removeClass("running");
175 175 $([IPython.events]).trigger('set_dirty.Notebook', {'value': true});
176 176 }
177 177
178 178 CodeCell.prototype._handle_set_next_input = function (text) {
179 179 var data = {'cell': this, 'text': text}
180 180 $([IPython.events]).trigger('set_next_input.Notebook', data);
181 181 }
182 182
183 183 // Basic cell manipulation.
184 184
185 185 CodeCell.prototype.select = function () {
186 186 IPython.Cell.prototype.select.apply(this);
187 187 this.code_mirror.refresh();
188 188 this.code_mirror.focus();
189 189 this.auto_highlight();
190 190 // We used to need an additional refresh() after the focus, but
191 191 // it appears that this has been fixed in CM. This bug would show
192 192 // up on FF when a newly loaded markdown cell was edited.
193 193 };
194 194
195 195
196 196 CodeCell.prototype.select_all = function () {
197 197 var start = {line: 0, ch: 0};
198 198 var nlines = this.code_mirror.lineCount();
199 199 var last_line = this.code_mirror.getLine(nlines-1);
200 200 var end = {line: nlines-1, ch: last_line.length};
201 201 this.code_mirror.setSelection(start, end);
202 202 };
203 203
204 204
205 205 CodeCell.prototype.collapse = function () {
206 206 this.collapsed = true;
207 207 this.output_area.collapse();
208 208 };
209 209
210 210
211 211 CodeCell.prototype.expand = function () {
212 212 this.collapsed = false;
213 213 this.output_area.expand();
214 214 };
215 215
216 216
217 217 CodeCell.prototype.toggle_output = function () {
218 218 this.collapsed = Boolean(1 - this.collapsed);
219 219 this.output_area.toggle_output();
220 220 };
221 221
222 222
223 223 CodeCell.prototype.toggle_output_scroll = function () {
224 224 this.output_area.toggle_scroll();
225 225 };
226 226
227 227
228
229
230
231 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
232 var ns = prompt_value || "&nbsp;";
233 return 'In&nbsp;[' + ns + ']:'
234 };
235
236 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
237 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
238 for(var i=1; i < lines_number; i++){html.push(['...:'])};
239 return html.join('</br>')
240 };
241
242 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
243
244
228 245 CodeCell.prototype.set_input_prompt = function (number) {
229 this.input_prompt_number = number;
230 var ns = number || "&nbsp;";
231 this.element.find('div.input_prompt').html('In&nbsp;[' + ns + ']:');
246 var nline = 1
247 if( this.code_mirror != undefined) {
248 nline = this.code_mirror.lineCount();
249 }
250 if (number){
251 this.input_prompt_number = number;
252 }
253 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
254 this.element.find('div.input_prompt').html(prompt_html);
232 255 };
233 256
234 257
235 258 CodeCell.prototype.clear_input = function () {
236 259 this.code_mirror.setValue('');
237 260 };
238 261
239 262
240 263 CodeCell.prototype.get_text = function () {
241 264 return this.code_mirror.getValue();
242 265 };
243 266
244 267
245 268 CodeCell.prototype.set_text = function (code) {
246 269 return this.code_mirror.setValue(code);
247 270 };
248 271
249 272
250 273 CodeCell.prototype.at_top = function () {
251 274 var cursor = this.code_mirror.getCursor();
252 275 if (cursor.line === 0 && cursor.ch === 0) {
253 276 return true;
254 277 } else {
255 278 return false;
256 279 }
257 280 };
258 281
259 282
260 283 CodeCell.prototype.at_bottom = function () {
261 284 var cursor = this.code_mirror.getCursor();
262 285 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
263 286 return true;
264 287 } else {
265 288 return false;
266 289 }
267 290 };
268 291
269 292
270 293 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
271 294 this.output_area.clear_output(stdout, stderr, other);
272 295 };
273 296
274 297
275 298 // JSON serialization
276 299
277 300 CodeCell.prototype.fromJSON = function (data) {
278 301 IPython.Cell.prototype.fromJSON.apply(this, arguments);
279 302 if (data.cell_type === 'code') {
280 303 if (data.input !== undefined) {
281 304 this.set_text(data.input);
282 305 // make this value the starting point, so that we can only undo
283 306 // to this state, instead of a blank cell
284 307 this.code_mirror.clearHistory();
285 308 this.auto_highlight();
286 309 }
287 310 if (data.prompt_number !== undefined) {
288 311 this.set_input_prompt(data.prompt_number);
289 312 } else {
290 313 this.set_input_prompt();
291 314 };
292 315 this.output_area.fromJSON(data.outputs);
293 316 if (data.collapsed !== undefined) {
294 317 if (data.collapsed) {
295 318 this.collapse();
296 319 } else {
297 320 this.expand();
298 321 };
299 322 };
300 323 };
301 324 };
302 325
303 326
304 327 CodeCell.prototype.toJSON = function () {
305 328 var data = IPython.Cell.prototype.toJSON.apply(this);
306 329 data.input = this.get_text();
307 330 data.cell_type = 'code';
308 331 if (this.input_prompt_number) {
309 332 data.prompt_number = this.input_prompt_number;
310 333 };
311 334 var outputs = this.output_area.toJSON();
312 335 data.outputs = outputs;
313 336 data.language = 'python';
314 337 data.collapsed = this.collapsed;
315 338 return data;
316 339 };
317 340
318 341
319 342 IPython.CodeCell = CodeCell;
320 343
321 344 return IPython;
322 345 }(IPython));
@@ -1,1305 +1,1310 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15 var key = IPython.utils.keycodes;
16 16
17 17 var Notebook = function (selector) {
18 18 this.read_only = IPython.read_only;
19 19 this.element = $(selector);
20 20 this.element.scroll();
21 21 this.element.data("notebook", this);
22 22 this.next_prompt_number = 1;
23 23 this.kernel = null;
24 24 this.clipboard = null;
25 25 this.paste_enabled = false;
26 26 this.dirty = false;
27 27 this.metadata = {};
28 28 // single worksheet for now
29 29 this.worksheet_metadata = {};
30 30 this.control_key_active = false;
31 31 this.notebook_id = null;
32 32 this.notebook_name = null;
33 33 this.notebook_name_blacklist_re = /[\/\\:]/;
34 34 this.nbformat = 3 // Increment this when changing the nbformat
35 35 this.nbformat_minor = 0 // Increment this when changing the nbformat
36 36 this.style();
37 37 this.create_elements();
38 38 this.bind_events();
39 39 };
40 40
41 41
42 42 Notebook.prototype.style = function () {
43 43 $('div#notebook').addClass('border-box-sizing');
44 44 };
45 45
46 46
47 47 Notebook.prototype.create_elements = function () {
48 48 // We add this end_space div to the end of the notebook div to:
49 49 // i) provide a margin between the last cell and the end of the notebook
50 50 // ii) to prevent the div from scrolling up when the last cell is being
51 51 // edited, but is too low on the page, which browsers will do automatically.
52 52 var that = this;
53 53 var end_space = $('<div/>').addClass('end_space').height("30%");
54 54 end_space.dblclick(function (e) {
55 55 if (that.read_only) return;
56 56 var ncells = that.ncells();
57 57 that.insert_cell_below('code',ncells-1);
58 58 });
59 59 this.element.append(end_space);
60 60 $('div#notebook').addClass('border-box-sizing');
61 61 };
62 62
63 63
64 64 Notebook.prototype.bind_events = function () {
65 65 var that = this;
66 66
67 67 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
68 68 var index = that.find_cell_index(data.cell);
69 69 var new_cell = that.insert_cell_below('code',index);
70 70 new_cell.set_text(data.text);
71 71 that.dirty = true;
72 72 });
73 73
74 74 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
75 75 that.dirty = data.value;
76 76 });
77 77
78 78 $([IPython.events]).on('select.Cell', function (event, data) {
79 79 var index = that.find_cell_index(data.cell);
80 80 that.select(index);
81 81 });
82 82
83 83
84 84 $(document).keydown(function (event) {
85 85 // console.log(event);
86 86 if (that.read_only) return true;
87 87
88 88 // Save (CTRL+S) or (AppleKey+S)
89 89 //metaKey = applekey on mac
90 90 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
91 91 that.save_notebook();
92 92 event.preventDefault();
93 93 return false;
94 94 } else if (event.which === key.ESC) {
95 95 // Intercept escape at highest level to avoid closing
96 96 // websocket connection with firefox
97 97 event.preventDefault();
98 98 } else if (event.which === key.SHIFT) {
99 99 // ignore shift keydown
100 100 return true;
101 101 }
102 102 if (event.which === key.UPARROW && !event.shiftKey) {
103 103 var cell = that.get_selected_cell();
104 104 if (cell.at_top()) {
105 105 event.preventDefault();
106 106 that.select_prev();
107 107 };
108 108 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
109 109 var cell = that.get_selected_cell();
110 110 if (cell.at_bottom()) {
111 111 event.preventDefault();
112 112 that.select_next();
113 113 };
114 114 } else if (event.which === key.ENTER && event.shiftKey) {
115 115 that.execute_selected_cell();
116 116 return false;
117 117 } else if (event.which === key.ENTER && event.altKey) {
118 118 // Execute code cell, and insert new in place
119 119 that.execute_selected_cell();
120 120 // Only insert a new cell, if we ended up in an already populated cell
121 121 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
122 122 that.insert_cell_above('code');
123 123 }
124 124 return false;
125 125 } else if (event.which === key.ENTER && event.ctrlKey) {
126 126 that.execute_selected_cell({terminal:true});
127 127 return false;
128 128 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
129 129 that.control_key_active = true;
130 130 return false;
131 131 } else if (event.which === 88 && that.control_key_active) {
132 132 // Cut selected cell = x
133 133 that.cut_cell();
134 134 that.control_key_active = false;
135 135 return false;
136 136 } else if (event.which === 67 && that.control_key_active) {
137 137 // Copy selected cell = c
138 138 that.copy_cell();
139 139 that.control_key_active = false;
140 140 return false;
141 141 } else if (event.which === 86 && that.control_key_active) {
142 142 // Paste selected cell = v
143 143 that.paste_cell();
144 144 that.control_key_active = false;
145 145 return false;
146 146 } else if (event.which === 68 && that.control_key_active) {
147 147 // Delete selected cell = d
148 148 that.delete_cell();
149 149 that.control_key_active = false;
150 150 return false;
151 151 } else if (event.which === 65 && that.control_key_active) {
152 152 // Insert code cell above selected = a
153 153 that.insert_cell_above('code');
154 154 that.control_key_active = false;
155 155 return false;
156 156 } else if (event.which === 66 && that.control_key_active) {
157 157 // Insert code cell below selected = b
158 158 that.insert_cell_below('code');
159 159 that.control_key_active = false;
160 160 return false;
161 161 } else if (event.which === 89 && that.control_key_active) {
162 162 // To code = y
163 163 that.to_code();
164 164 that.control_key_active = false;
165 165 return false;
166 166 } else if (event.which === 77 && that.control_key_active) {
167 167 // To markdown = m
168 168 that.to_markdown();
169 169 that.control_key_active = false;
170 170 return false;
171 171 } else if (event.which === 84 && that.control_key_active) {
172 172 // To Raw = t
173 173 that.to_raw();
174 174 that.control_key_active = false;
175 175 return false;
176 176 } else if (event.which === 49 && that.control_key_active) {
177 177 // To Heading 1 = 1
178 178 that.to_heading(undefined, 1);
179 179 that.control_key_active = false;
180 180 return false;
181 181 } else if (event.which === 50 && that.control_key_active) {
182 182 // To Heading 2 = 2
183 183 that.to_heading(undefined, 2);
184 184 that.control_key_active = false;
185 185 return false;
186 186 } else if (event.which === 51 && that.control_key_active) {
187 187 // To Heading 3 = 3
188 188 that.to_heading(undefined, 3);
189 189 that.control_key_active = false;
190 190 return false;
191 191 } else if (event.which === 52 && that.control_key_active) {
192 192 // To Heading 4 = 4
193 193 that.to_heading(undefined, 4);
194 194 that.control_key_active = false;
195 195 return false;
196 196 } else if (event.which === 53 && that.control_key_active) {
197 197 // To Heading 5 = 5
198 198 that.to_heading(undefined, 5);
199 199 that.control_key_active = false;
200 200 return false;
201 201 } else if (event.which === 54 && that.control_key_active) {
202 202 // To Heading 6 = 6
203 203 that.to_heading(undefined, 6);
204 204 that.control_key_active = false;
205 205 return false;
206 206 } else if (event.which === 79 && that.control_key_active) {
207 207 // Toggle output = o
208 208 if (event.shiftKey){
209 209 that.toggle_output_scroll();
210 210 } else {
211 211 that.toggle_output();
212 212 }
213 213 that.control_key_active = false;
214 214 return false;
215 215 } else if (event.which === 83 && that.control_key_active) {
216 216 // Save notebook = s
217 217 that.save_notebook();
218 218 that.control_key_active = false;
219 219 return false;
220 220 } else if (event.which === 74 && that.control_key_active) {
221 221 // Move cell down = j
222 222 that.move_cell_down();
223 223 that.control_key_active = false;
224 224 return false;
225 225 } else if (event.which === 75 && that.control_key_active) {
226 226 // Move cell up = k
227 227 that.move_cell_up();
228 228 that.control_key_active = false;
229 229 return false;
230 230 } else if (event.which === 80 && that.control_key_active) {
231 231 // Select previous = p
232 232 that.select_prev();
233 233 that.control_key_active = false;
234 234 return false;
235 235 } else if (event.which === 78 && that.control_key_active) {
236 236 // Select next = n
237 237 that.select_next();
238 238 that.control_key_active = false;
239 239 return false;
240 240 } else if (event.which === 76 && that.control_key_active) {
241 241 // Toggle line numbers = l
242 242 that.cell_toggle_line_numbers();
243 243 that.control_key_active = false;
244 244 return false;
245 245 } else if (event.which === 73 && that.control_key_active) {
246 246 // Interrupt kernel = i
247 247 that.kernel.interrupt();
248 248 that.control_key_active = false;
249 249 return false;
250 250 } else if (event.which === 190 && that.control_key_active) {
251 251 // Restart kernel = . # matches qt console
252 252 that.restart_kernel();
253 253 that.control_key_active = false;
254 254 return false;
255 255 } else if (event.which === 72 && that.control_key_active) {
256 256 // Show keyboard shortcuts = h
257 257 IPython.quick_help.show_keyboard_shortcuts();
258 258 that.control_key_active = false;
259 259 return false;
260 260 } else if (that.control_key_active) {
261 261 that.control_key_active = false;
262 262 return true;
263 263 };
264 264 return true;
265 265 });
266 266
267 267 var collapse_time = function(time){
268 268 var app_height = $('div#main_app').height(); // content height
269 269 var splitter_height = $('div#pager_splitter').outerHeight(true);
270 270 var new_height = app_height - splitter_height;
271 271 that.element.animate({height : new_height + 'px'}, time);
272 272 }
273 273
274 274 this.element.bind('collapse_pager', function (event,extrap) {
275 275 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
276 276 collapse_time(time);
277 277 });
278 278
279 279 var expand_time = function(time) {
280 280 var app_height = $('div#main_app').height(); // content height
281 281 var splitter_height = $('div#pager_splitter').outerHeight(true);
282 282 var pager_height = $('div#pager').outerHeight(true);
283 283 var new_height = app_height - pager_height - splitter_height;
284 284 that.element.animate({height : new_height + 'px'}, time);
285 285 }
286 286
287 287 this.element.bind('expand_pager', function (event, extrap) {
288 288 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
289 289 expand_time(time);
290 290 });
291 291
292 292 $(window).bind('beforeunload', function () {
293 293 // TODO: Make killing the kernel configurable.
294 294 var kill_kernel = false;
295 295 if (kill_kernel) {
296 296 that.kernel.kill();
297 297 }
298 298 if (that.dirty && ! that.read_only) {
299 299 return "You have unsaved changes that will be lost if you leave this page.";
300 300 };
301 301 // Null is the *only* return value that will make the browser not
302 302 // pop up the "don't leave" dialog.
303 303 return null;
304 304 });
305 305 };
306 306
307 307 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
308 308 var cells = this.get_cells();
309 309 var time = time || 0;
310 310 cell_number = Math.min(cells.length-1,cell_number);
311 311 cell_number = Math.max(0 ,cell_number);
312 312 scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
313 313 this.element.animate({scrollTop:scroll_value}, time);
314 314 return scroll_value;
315 315 };
316 316
317 317
318 318 Notebook.prototype.scroll_to_bottom = function () {
319 319 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
320 320 };
321 321
322 322
323 323 Notebook.prototype.scroll_to_top = function () {
324 324 this.element.animate({scrollTop:0}, 0);
325 325 };
326 326
327 327
328 328 // Cell indexing, retrieval, etc.
329 329
330 330 Notebook.prototype.get_cell_elements = function () {
331 331 return this.element.children("div.cell");
332 332 };
333 333
334 334
335 335 Notebook.prototype.get_cell_element = function (index) {
336 336 var result = null;
337 337 var e = this.get_cell_elements().eq(index);
338 338 if (e.length !== 0) {
339 339 result = e;
340 340 }
341 341 return result;
342 342 };
343 343
344 344
345 345 Notebook.prototype.ncells = function (cell) {
346 346 return this.get_cell_elements().length;
347 347 };
348 348
349 349
350 350 // TODO: we are often calling cells as cells()[i], which we should optimize
351 351 // to cells(i) or a new method.
352 352 Notebook.prototype.get_cells = function () {
353 353 return this.get_cell_elements().toArray().map(function (e) {
354 354 return $(e).data("cell");
355 355 });
356 356 };
357 357
358 358
359 359 Notebook.prototype.get_cell = function (index) {
360 360 var result = null;
361 361 var ce = this.get_cell_element(index);
362 362 if (ce !== null) {
363 363 result = ce.data('cell');
364 364 }
365 365 return result;
366 366 }
367 367
368 368
369 369 Notebook.prototype.get_next_cell = function (cell) {
370 370 var result = null;
371 371 var index = this.find_cell_index(cell);
372 372 if (index !== null && index < this.ncells()) {
373 373 result = this.get_cell(index+1);
374 374 }
375 375 return result;
376 376 }
377 377
378 378
379 379 Notebook.prototype.get_prev_cell = function (cell) {
380 380 var result = null;
381 381 var index = this.find_cell_index(cell);
382 382 if (index !== null && index > 1) {
383 383 result = this.get_cell(index-1);
384 384 }
385 385 return result;
386 386 }
387 387
388 388 Notebook.prototype.find_cell_index = function (cell) {
389 389 var result = null;
390 390 this.get_cell_elements().filter(function (index) {
391 391 if ($(this).data("cell") === cell) {
392 392 result = index;
393 393 };
394 394 });
395 395 return result;
396 396 };
397 397
398 398
399 399 Notebook.prototype.index_or_selected = function (index) {
400 400 var i;
401 401 if (index === undefined || index === null) {
402 402 i = this.get_selected_index();
403 403 if (i === null) {
404 404 i = 0;
405 405 }
406 406 } else {
407 407 i = index;
408 408 }
409 409 return i;
410 410 };
411 411
412 412
413 413 Notebook.prototype.get_selected_cell = function () {
414 414 var index = this.get_selected_index();
415 415 return this.get_cell(index);
416 416 };
417 417
418 418
419 419 Notebook.prototype.is_valid_cell_index = function (index) {
420 420 if (index !== null && index >= 0 && index < this.ncells()) {
421 421 return true;
422 422 } else {
423 423 return false;
424 424 };
425 425 }
426 426
427 427 Notebook.prototype.get_selected_index = function () {
428 428 var result = null;
429 429 this.get_cell_elements().filter(function (index) {
430 430 if ($(this).data("cell").selected === true) {
431 431 result = index;
432 432 };
433 433 });
434 434 return result;
435 435 };
436 436
437 437
438 438 // Cell selection.
439 439
440 440 Notebook.prototype.select = function (index) {
441 441 if (index !== undefined && index >= 0 && index < this.ncells()) {
442 442 sindex = this.get_selected_index()
443 443 if (sindex !== null && index !== sindex) {
444 444 this.get_cell(sindex).unselect();
445 445 };
446 446 var cell = this.get_cell(index)
447 447 cell.select();
448 448 if (cell.cell_type === 'heading') {
449 449 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
450 450 {'cell_type':cell.cell_type,level:cell.level}
451 451 );
452 452 } else {
453 453 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
454 454 {'cell_type':cell.cell_type}
455 455 );
456 456 };
457 457 };
458 458 return this;
459 459 };
460 460
461 461
462 462 Notebook.prototype.select_next = function () {
463 463 var index = this.get_selected_index();
464 464 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
465 465 this.select(index+1);
466 466 };
467 467 return this;
468 468 };
469 469
470 470
471 471 Notebook.prototype.select_prev = function () {
472 472 var index = this.get_selected_index();
473 473 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
474 474 this.select(index-1);
475 475 };
476 476 return this;
477 477 };
478 478
479 479
480 480 // Cell movement
481 481
482 482 Notebook.prototype.move_cell_up = function (index) {
483 483 var i = this.index_or_selected();
484 484 if (i !== null && i < this.ncells() && i > 0) {
485 485 var pivot = this.get_cell_element(i-1);
486 486 var tomove = this.get_cell_element(i);
487 487 if (pivot !== null && tomove !== null) {
488 488 tomove.detach();
489 489 pivot.before(tomove);
490 490 this.select(i-1);
491 491 };
492 492 };
493 493 this.dirty = true;
494 494 return this;
495 495 };
496 496
497 497
498 498 Notebook.prototype.move_cell_down = function (index) {
499 499 var i = this.index_or_selected();
500 500 if (i !== null && i < (this.ncells()-1) && i >= 0) {
501 501 var pivot = this.get_cell_element(i+1);
502 502 var tomove = this.get_cell_element(i);
503 503 if (pivot !== null && tomove !== null) {
504 504 tomove.detach();
505 505 pivot.after(tomove);
506 506 this.select(i+1);
507 507 };
508 508 };
509 509 this.dirty = true;
510 510 return this;
511 511 };
512 512
513 513
514 514 Notebook.prototype.sort_cells = function () {
515 515 // This is not working right now. Calling this will actually crash
516 516 // the browser. I think there is an infinite loop in here...
517 517 var ncells = this.ncells();
518 518 var sindex = this.get_selected_index();
519 519 var swapped;
520 520 do {
521 521 swapped = false;
522 522 for (var i=1; i<ncells; i++) {
523 523 current = this.get_cell(i);
524 524 previous = this.get_cell(i-1);
525 525 if (previous.input_prompt_number > current.input_prompt_number) {
526 526 this.move_cell_up(i);
527 527 swapped = true;
528 528 };
529 529 };
530 530 } while (swapped);
531 531 this.select(sindex);
532 532 return this;
533 533 };
534 534
535 535 // Insertion, deletion.
536 536
537 537 Notebook.prototype.delete_cell = function (index) {
538 538 var i = this.index_or_selected(index);
539 539 if (this.is_valid_cell_index(i)) {
540 540 var ce = this.get_cell_element(i);
541 541 ce.remove();
542 542 if (i === (this.ncells())) {
543 543 this.select(i-1);
544 544 } else {
545 545 this.select(i);
546 546 };
547 547 this.dirty = true;
548 548 };
549 549 return this;
550 550 };
551 551
552 552
553 Notebook.prototype.insert_cell_at_bottom = function (type){
554 var len = this.ncells();
555 return this.insert_cell_below(type,len-1);
556 }
557
553 558 Notebook.prototype.insert_cell_below = function (type, index) {
554 559 // type = ('code','html','markdown')
555 560 // index = cell index or undefined to insert below selected
556 561 index = this.index_or_selected(index);
557 562 var cell = null;
558 563 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
559 564 if (type === 'code') {
560 565 cell = new IPython.CodeCell(this.kernel);
561 566 cell.set_input_prompt();
562 567 } else if (type === 'markdown') {
563 568 cell = new IPython.MarkdownCell();
564 569 } else if (type === 'html') {
565 570 cell = new IPython.HTMLCell();
566 571 } else if (type === 'raw') {
567 572 cell = new IPython.RawCell();
568 573 } else if (type === 'heading') {
569 574 cell = new IPython.HeadingCell();
570 575 };
571 576 if (cell !== null) {
572 577 if (this.ncells() === 0) {
573 578 this.element.find('div.end_space').before(cell.element);
574 579 } else if (this.is_valid_cell_index(index)) {
575 580 this.get_cell_element(index).after(cell.element);
576 581 };
577 582 cell.render();
578 583 this.select(this.find_cell_index(cell));
579 584 this.dirty = true;
580 585 return cell;
581 586 };
582 587 };
583 588 return cell;
584 589 };
585 590
586 591
587 592 Notebook.prototype.insert_cell_above = function (type, index) {
588 593 // type = ('code','html','markdown')
589 594 // index = cell index or undefined to insert above selected
590 595 index = this.index_or_selected(index);
591 596 var cell = null;
592 597 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
593 598 if (type === 'code') {
594 599 cell = new IPython.CodeCell(this.kernel);
595 600 cell.set_input_prompt();
596 601 } else if (type === 'markdown') {
597 602 cell = new IPython.MarkdownCell();
598 603 } else if (type === 'html') {
599 604 cell = new IPython.HTMLCell();
600 605 } else if (type === 'raw') {
601 606 cell = new IPython.RawCell();
602 607 } else if (type === 'heading') {
603 608 cell = new IPython.HeadingCell();
604 609 };
605 610 if (cell !== null) {
606 611 if (this.ncells() === 0) {
607 612 this.element.find('div.end_space').before(cell.element);
608 613 } else if (this.is_valid_cell_index(index)) {
609 614 this.get_cell_element(index).before(cell.element);
610 615 };
611 616 cell.render();
612 617 this.select(this.find_cell_index(cell));
613 618 this.dirty = true;
614 619 return cell;
615 620 };
616 621 };
617 622 return cell;
618 623 };
619 624
620 625
621 626 Notebook.prototype.to_code = function (index) {
622 627 var i = this.index_or_selected(index);
623 628 if (this.is_valid_cell_index(i)) {
624 629 var source_element = this.get_cell_element(i);
625 630 var source_cell = source_element.data("cell");
626 631 if (!(source_cell instanceof IPython.CodeCell)) {
627 632 target_cell = this.insert_cell_below('code',i);
628 633 var text = source_cell.get_text();
629 634 if (text === source_cell.placeholder) {
630 635 text = '';
631 636 }
632 637 target_cell.set_text(text);
633 638 // make this value the starting point, so that we can only undo
634 639 // to this state, instead of a blank cell
635 640 target_cell.code_mirror.clearHistory();
636 641 source_element.remove();
637 642 this.dirty = true;
638 643 };
639 644 };
640 645 };
641 646
642 647
643 648 Notebook.prototype.to_markdown = function (index) {
644 649 var i = this.index_or_selected(index);
645 650 if (this.is_valid_cell_index(i)) {
646 651 var source_element = this.get_cell_element(i);
647 652 var source_cell = source_element.data("cell");
648 653 if (!(source_cell instanceof IPython.MarkdownCell)) {
649 654 target_cell = this.insert_cell_below('markdown',i);
650 655 var text = source_cell.get_text();
651 656 if (text === source_cell.placeholder) {
652 657 text = '';
653 658 };
654 659 // The edit must come before the set_text.
655 660 target_cell.edit();
656 661 target_cell.set_text(text);
657 662 // make this value the starting point, so that we can only undo
658 663 // to this state, instead of a blank cell
659 664 target_cell.code_mirror.clearHistory();
660 665 source_element.remove();
661 666 this.dirty = true;
662 667 };
663 668 };
664 669 };
665 670
666 671
667 672 Notebook.prototype.to_html = function (index) {
668 673 var i = this.index_or_selected(index);
669 674 if (this.is_valid_cell_index(i)) {
670 675 var source_element = this.get_cell_element(i);
671 676 var source_cell = source_element.data("cell");
672 677 var target_cell = null;
673 678 if (!(source_cell instanceof IPython.HTMLCell)) {
674 679 target_cell = this.insert_cell_below('html',i);
675 680 var text = source_cell.get_text();
676 681 if (text === source_cell.placeholder) {
677 682 text = '';
678 683 };
679 684 // The edit must come before the set_text.
680 685 target_cell.edit();
681 686 target_cell.set_text(text);
682 687 // make this value the starting point, so that we can only undo
683 688 // to this state, instead of a blank cell
684 689 target_cell.code_mirror.clearHistory();
685 690 source_element.remove();
686 691 this.dirty = true;
687 692 };
688 693 };
689 694 };
690 695
691 696
692 697 Notebook.prototype.to_raw = function (index) {
693 698 var i = this.index_or_selected(index);
694 699 if (this.is_valid_cell_index(i)) {
695 700 var source_element = this.get_cell_element(i);
696 701 var source_cell = source_element.data("cell");
697 702 var target_cell = null;
698 703 if (!(source_cell instanceof IPython.RawCell)) {
699 704 target_cell = this.insert_cell_below('raw',i);
700 705 var text = source_cell.get_text();
701 706 if (text === source_cell.placeholder) {
702 707 text = '';
703 708 };
704 709 // The edit must come before the set_text.
705 710 target_cell.edit();
706 711 target_cell.set_text(text);
707 712 // make this value the starting point, so that we can only undo
708 713 // to this state, instead of a blank cell
709 714 target_cell.code_mirror.clearHistory();
710 715 source_element.remove();
711 716 this.dirty = true;
712 717 };
713 718 };
714 719 };
715 720
716 721
717 722 Notebook.prototype.to_heading = function (index, level) {
718 723 level = level || 1;
719 724 var i = this.index_or_selected(index);
720 725 if (this.is_valid_cell_index(i)) {
721 726 var source_element = this.get_cell_element(i);
722 727 var source_cell = source_element.data("cell");
723 728 var target_cell = null;
724 729 if (source_cell instanceof IPython.HeadingCell) {
725 730 source_cell.set_level(level);
726 731 } else {
727 732 target_cell = this.insert_cell_below('heading',i);
728 733 var text = source_cell.get_text();
729 734 if (text === source_cell.placeholder) {
730 735 text = '';
731 736 };
732 737 // The edit must come before the set_text.
733 738 target_cell.set_level(level);
734 739 target_cell.edit();
735 740 target_cell.set_text(text);
736 741 // make this value the starting point, so that we can only undo
737 742 // to this state, instead of a blank cell
738 743 target_cell.code_mirror.clearHistory();
739 744 source_element.remove();
740 745 this.dirty = true;
741 746 };
742 747 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
743 748 {'cell_type':'heading',level:level}
744 749 );
745 750 };
746 751 };
747 752
748 753
749 754 // Cut/Copy/Paste
750 755
751 756 Notebook.prototype.enable_paste = function () {
752 757 var that = this;
753 758 if (!this.paste_enabled) {
754 759 $('#paste_cell').removeClass('ui-state-disabled')
755 760 .on('click', function () {that.paste_cell();});
756 761 $('#paste_cell_above').removeClass('ui-state-disabled')
757 762 .on('click', function () {that.paste_cell_above();});
758 763 $('#paste_cell_below').removeClass('ui-state-disabled')
759 764 .on('click', function () {that.paste_cell_below();});
760 765 this.paste_enabled = true;
761 766 };
762 767 };
763 768
764 769
765 770 Notebook.prototype.disable_paste = function () {
766 771 if (this.paste_enabled) {
767 772 $('#paste_cell').addClass('ui-state-disabled').off('click');
768 773 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
769 774 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
770 775 this.paste_enabled = false;
771 776 };
772 777 };
773 778
774 779
775 780 Notebook.prototype.cut_cell = function () {
776 781 this.copy_cell();
777 782 this.delete_cell();
778 783 }
779 784
780 785 Notebook.prototype.copy_cell = function () {
781 786 var cell = this.get_selected_cell();
782 787 this.clipboard = cell.toJSON();
783 788 this.enable_paste();
784 789 };
785 790
786 791
787 792 Notebook.prototype.paste_cell = function () {
788 793 if (this.clipboard !== null && this.paste_enabled) {
789 794 var cell_data = this.clipboard;
790 795 var new_cell = this.insert_cell_above(cell_data.cell_type);
791 796 new_cell.fromJSON(cell_data);
792 797 old_cell = this.get_next_cell(new_cell);
793 798 this.delete_cell(this.find_cell_index(old_cell));
794 799 this.select(this.find_cell_index(new_cell));
795 800 };
796 801 };
797 802
798 803
799 804 Notebook.prototype.paste_cell_above = function () {
800 805 if (this.clipboard !== null && this.paste_enabled) {
801 806 var cell_data = this.clipboard;
802 807 var new_cell = this.insert_cell_above(cell_data.cell_type);
803 808 new_cell.fromJSON(cell_data);
804 809 };
805 810 };
806 811
807 812
808 813 Notebook.prototype.paste_cell_below = function () {
809 814 if (this.clipboard !== null && this.paste_enabled) {
810 815 var cell_data = this.clipboard;
811 816 var new_cell = this.insert_cell_below(cell_data.cell_type);
812 817 new_cell.fromJSON(cell_data);
813 818 };
814 819 };
815 820
816 821
817 822 // Split/merge
818 823
819 824 Notebook.prototype.split_cell = function () {
820 825 // Todo: implement spliting for other cell types.
821 826 var cell = this.get_selected_cell();
822 827 if (cell.is_splittable()) {
823 828 texta = cell.get_pre_cursor();
824 829 textb = cell.get_post_cursor();
825 830 if (cell instanceof IPython.CodeCell) {
826 831 cell.set_text(texta);
827 832 var new_cell = this.insert_cell_below('code');
828 833 new_cell.set_text(textb);
829 834 } else if (cell instanceof IPython.MarkdownCell) {
830 835 cell.set_text(texta);
831 836 cell.render();
832 837 var new_cell = this.insert_cell_below('markdown');
833 838 new_cell.edit(); // editor must be visible to call set_text
834 839 new_cell.set_text(textb);
835 840 new_cell.render();
836 841 } else if (cell instanceof IPython.HTMLCell) {
837 842 cell.set_text(texta);
838 843 cell.render();
839 844 var new_cell = this.insert_cell_below('html');
840 845 new_cell.edit(); // editor must be visible to call set_text
841 846 new_cell.set_text(textb);
842 847 new_cell.render();
843 848 };
844 849 };
845 850 };
846 851
847 852
848 853 Notebook.prototype.merge_cell_above = function () {
849 854 var index = this.get_selected_index();
850 855 var cell = this.get_cell(index);
851 856 if (index > 0) {
852 857 upper_cell = this.get_cell(index-1);
853 858 upper_text = upper_cell.get_text();
854 859 text = cell.get_text();
855 860 if (cell instanceof IPython.CodeCell) {
856 861 cell.set_text(upper_text+'\n'+text);
857 862 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
858 863 cell.edit();
859 864 cell.set_text(upper_text+'\n'+text);
860 865 cell.render();
861 866 };
862 867 this.delete_cell(index-1);
863 868 this.select(this.find_cell_index(cell));
864 869 };
865 870 };
866 871
867 872
868 873 Notebook.prototype.merge_cell_below = function () {
869 874 var index = this.get_selected_index();
870 875 var cell = this.get_cell(index);
871 876 if (index < this.ncells()-1) {
872 877 lower_cell = this.get_cell(index+1);
873 878 lower_text = lower_cell.get_text();
874 879 text = cell.get_text();
875 880 if (cell instanceof IPython.CodeCell) {
876 881 cell.set_text(text+'\n'+lower_text);
877 882 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
878 883 cell.edit();
879 884 cell.set_text(text+'\n'+lower_text);
880 885 cell.render();
881 886 };
882 887 this.delete_cell(index+1);
883 888 this.select(this.find_cell_index(cell));
884 889 };
885 890 };
886 891
887 892
888 893 // Cell collapsing and output clearing
889 894
890 895 Notebook.prototype.collapse = function (index) {
891 896 var i = this.index_or_selected(index);
892 897 this.get_cell(i).collapse();
893 898 this.dirty = true;
894 899 };
895 900
896 901
897 902 Notebook.prototype.expand = function (index) {
898 903 var i = this.index_or_selected(index);
899 904 this.get_cell(i).expand();
900 905 this.dirty = true;
901 906 };
902 907
903 908
904 909 Notebook.prototype.toggle_output = function (index) {
905 910 var i = this.index_or_selected(index);
906 911 this.get_cell(i).toggle_output();
907 912 this.dirty = true;
908 913 };
909 914
910 915
911 916 Notebook.prototype.toggle_output_scroll = function (index) {
912 917 var i = this.index_or_selected(index);
913 918 this.get_cell(i).toggle_output_scroll();
914 919 };
915 920
916 921
917 922 Notebook.prototype.collapse_all_output = function () {
918 923 var ncells = this.ncells();
919 924 var cells = this.get_cells();
920 925 for (var i=0; i<ncells; i++) {
921 926 if (cells[i] instanceof IPython.CodeCell) {
922 927 cells[i].output_area.collapse();
923 928 }
924 929 };
925 930 // this should not be set if the `collapse` key is removed from nbformat
926 931 this.dirty = true;
927 932 };
928 933
929 934
930 935 Notebook.prototype.scroll_all_output = function () {
931 936 var ncells = this.ncells();
932 937 var cells = this.get_cells();
933 938 for (var i=0; i<ncells; i++) {
934 939 if (cells[i] instanceof IPython.CodeCell) {
935 940 cells[i].output_area.expand();
936 941 cells[i].output_area.scroll_if_long(20);
937 942 }
938 943 };
939 944 // this should not be set if the `collapse` key is removed from nbformat
940 945 this.dirty = true;
941 946 };
942 947
943 948
944 949 Notebook.prototype.expand_all_output = function () {
945 950 var ncells = this.ncells();
946 951 var cells = this.get_cells();
947 952 for (var i=0; i<ncells; i++) {
948 953 if (cells[i] instanceof IPython.CodeCell) {
949 954 cells[i].output_area.expand();
950 955 cells[i].output_area.unscroll_area();
951 956 }
952 957 };
953 958 // this should not be set if the `collapse` key is removed from nbformat
954 959 this.dirty = true;
955 960 };
956 961
957 962
958 963 Notebook.prototype.clear_all_output = function () {
959 964 var ncells = this.ncells();
960 965 var cells = this.get_cells();
961 966 for (var i=0; i<ncells; i++) {
962 967 if (cells[i] instanceof IPython.CodeCell) {
963 968 cells[i].clear_output(true,true,true);
964 969 // Make all In[] prompts blank, as well
965 970 // TODO: make this configurable (via checkbox?)
966 971 cells[i].set_input_prompt();
967 972 }
968 973 };
969 974 this.dirty = true;
970 975 };
971 976
972 977
973 978 // Other cell functions: line numbers, ...
974 979
975 980 Notebook.prototype.cell_toggle_line_numbers = function() {
976 981 this.get_selected_cell().toggle_line_numbers();
977 982 };
978 983
979 984 // Kernel related things
980 985
981 986 Notebook.prototype.start_kernel = function () {
982 987 var base_url = $('body').data('baseKernelUrl') + "kernels";
983 988 this.kernel = new IPython.Kernel(base_url);
984 989 this.kernel.start(this.notebook_id);
985 990 // Now that the kernel has been created, tell the CodeCells about it.
986 991 var ncells = this.ncells();
987 992 for (var i=0; i<ncells; i++) {
988 993 var cell = this.get_cell(i);
989 994 if (cell instanceof IPython.CodeCell) {
990 995 cell.set_kernel(this.kernel)
991 996 };
992 997 };
993 998 };
994 999
995 1000
996 1001 Notebook.prototype.restart_kernel = function () {
997 1002 var that = this;
998 1003 var dialog = $('<div/>');
999 1004 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
1000 1005 $(document).append(dialog);
1001 1006 dialog.dialog({
1002 1007 resizable: false,
1003 1008 modal: true,
1004 1009 title: "Restart kernel or continue running?",
1005 1010 closeText: '',
1006 1011 buttons : {
1007 1012 "Restart": function () {
1008 1013 that.kernel.restart();
1009 1014 $(this).dialog('close');
1010 1015 },
1011 1016 "Continue running": function () {
1012 1017 $(this).dialog('close');
1013 1018 }
1014 1019 }
1015 1020 });
1016 1021 };
1017 1022
1018 1023
1019 1024 Notebook.prototype.execute_selected_cell = function (options) {
1020 1025 // add_new: should a new cell be added if we are at the end of the nb
1021 1026 // terminal: execute in terminal mode, which stays in the current cell
1022 1027 default_options = {terminal: false, add_new: true};
1023 1028 $.extend(default_options, options);
1024 1029 var that = this;
1025 1030 var cell = that.get_selected_cell();
1026 1031 var cell_index = that.find_cell_index(cell);
1027 1032 if (cell instanceof IPython.CodeCell) {
1028 1033 cell.execute();
1029 1034 } else if (cell instanceof IPython.HTMLCell) {
1030 1035 cell.render();
1031 1036 }
1032 1037 if (default_options.terminal) {
1033 1038 cell.select_all();
1034 1039 } else {
1035 1040 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1036 1041 that.insert_cell_below('code');
1037 1042 // If we are adding a new cell at the end, scroll down to show it.
1038 1043 that.scroll_to_bottom();
1039 1044 } else {
1040 1045 that.select(cell_index+1);
1041 1046 };
1042 1047 };
1043 1048 this.dirty = true;
1044 1049 };
1045 1050
1046 1051
1047 1052 Notebook.prototype.execute_all_cells = function () {
1048 1053 var ncells = this.ncells();
1049 1054 for (var i=0; i<ncells; i++) {
1050 1055 this.select(i);
1051 1056 this.execute_selected_cell({add_new:false});
1052 1057 };
1053 1058 this.scroll_to_bottom();
1054 1059 };
1055 1060
1056 1061 // Persistance and loading
1057 1062
1058 1063 Notebook.prototype.get_notebook_id = function () {
1059 1064 return this.notebook_id;
1060 1065 };
1061 1066
1062 1067
1063 1068 Notebook.prototype.get_notebook_name = function () {
1064 1069 return this.notebook_name;
1065 1070 };
1066 1071
1067 1072
1068 1073 Notebook.prototype.set_notebook_name = function (name) {
1069 1074 this.notebook_name = name;
1070 1075 };
1071 1076
1072 1077
1073 1078 Notebook.prototype.test_notebook_name = function (nbname) {
1074 1079 nbname = nbname || '';
1075 1080 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1076 1081 return true;
1077 1082 } else {
1078 1083 return false;
1079 1084 };
1080 1085 };
1081 1086
1082 1087
1083 1088 Notebook.prototype.fromJSON = function (data) {
1084 1089 var ncells = this.ncells();
1085 1090 var i;
1086 1091 for (i=0; i<ncells; i++) {
1087 1092 // Always delete cell 0 as they get renumbered as they are deleted.
1088 1093 this.delete_cell(0);
1089 1094 };
1090 1095 // Save the metadata and name.
1091 1096 this.metadata = data.metadata;
1092 1097 this.notebook_name = data.metadata.name;
1093 1098 // Only handle 1 worksheet for now.
1094 1099 var worksheet = data.worksheets[0];
1095 1100 if (worksheet !== undefined) {
1096 1101 if (worksheet.metadata) {
1097 1102 this.worksheet_metadata = worksheet.metadata;
1098 1103 }
1099 1104 var new_cells = worksheet.cells;
1100 1105 ncells = new_cells.length;
1101 1106 var cell_data = null;
1102 1107 var new_cell = null;
1103 1108 for (i=0; i<ncells; i++) {
1104 1109 cell_data = new_cells[i];
1105 1110 // VERSIONHACK: plaintext -> raw
1106 1111 // handle never-released plaintext name for raw cells
1107 1112 if (cell_data.cell_type === 'plaintext'){
1108 1113 cell_data.cell_type = 'raw';
1109 1114 }
1110 1115
1111 1116 new_cell = this.insert_cell_below(cell_data.cell_type);
1112 1117 new_cell.fromJSON(cell_data);
1113 1118 };
1114 1119 };
1115 1120 if (data.worksheets.length > 1) {
1116 1121 var dialog = $('<div/>');
1117 1122 dialog.html("This notebook has " + data.worksheets.length + " worksheets, " +
1118 1123 "but this version of IPython can only handle the first. " +
1119 1124 "If you save this notebook, worksheets after the first will be lost."
1120 1125 );
1121 1126 this.element.append(dialog);
1122 1127 dialog.dialog({
1123 1128 resizable: false,
1124 1129 modal: true,
1125 1130 title: "Multiple worksheets",
1126 1131 closeText: "",
1127 1132 close: function(event, ui) {$(this).dialog('destroy').remove();},
1128 1133 buttons : {
1129 1134 "OK": function () {
1130 1135 $(this).dialog('close');
1131 1136 }
1132 1137 },
1133 1138 width: 400
1134 1139 });
1135 1140 }
1136 1141 };
1137 1142
1138 1143
1139 1144 Notebook.prototype.toJSON = function () {
1140 1145 var cells = this.get_cells();
1141 1146 var ncells = cells.length;
1142 1147 var cell_array = new Array(ncells);
1143 1148 for (var i=0; i<ncells; i++) {
1144 1149 cell_array[i] = cells[i].toJSON();
1145 1150 };
1146 1151 var data = {
1147 1152 // Only handle 1 worksheet for now.
1148 1153 worksheets : [{
1149 1154 cells: cell_array,
1150 1155 metadata: this.worksheet_metadata
1151 1156 }],
1152 1157 metadata : this.metadata
1153 1158 };
1154 1159 return data;
1155 1160 };
1156 1161
1157 1162 Notebook.prototype.save_notebook = function () {
1158 1163 // We may want to move the name/id/nbformat logic inside toJSON?
1159 1164 var data = this.toJSON();
1160 1165 data.metadata.name = this.notebook_name;
1161 1166 data.nbformat = this.nbformat;
1162 1167 data.nbformat_minor = this.nbformat_minor;
1163 1168 // We do the call with settings so we can set cache to false.
1164 1169 var settings = {
1165 1170 processData : false,
1166 1171 cache : false,
1167 1172 type : "PUT",
1168 1173 data : JSON.stringify(data),
1169 1174 headers : {'Content-Type': 'application/json'},
1170 1175 success : $.proxy(this.save_notebook_success,this),
1171 1176 error : $.proxy(this.save_notebook_error,this)
1172 1177 };
1173 1178 $([IPython.events]).trigger('notebook_saving.Notebook');
1174 1179 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1175 1180 $.ajax(url, settings);
1176 1181 };
1177 1182
1178 1183
1179 1184 Notebook.prototype.save_notebook_success = function (data, status, xhr) {
1180 1185 this.dirty = false;
1181 1186 $([IPython.events]).trigger('notebook_saved.Notebook');
1182 1187 };
1183 1188
1184 1189
1185 1190 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1186 1191 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1187 1192 };
1188 1193
1189 1194
1190 1195 Notebook.prototype.load_notebook = function (notebook_id) {
1191 1196 var that = this;
1192 1197 this.notebook_id = notebook_id;
1193 1198 // We do the call with settings so we can set cache to false.
1194 1199 var settings = {
1195 1200 processData : false,
1196 1201 cache : false,
1197 1202 type : "GET",
1198 1203 dataType : "json",
1199 1204 success : $.proxy(this.load_notebook_success,this),
1200 1205 error : $.proxy(this.load_notebook_error,this),
1201 1206 };
1202 1207 $([IPython.events]).trigger('notebook_loading.Notebook');
1203 1208 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1204 1209 $.ajax(url, settings);
1205 1210 };
1206 1211
1207 1212
1208 1213 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1209 1214 this.fromJSON(data);
1210 1215 if (this.ncells() === 0) {
1211 1216 this.insert_cell_below('code');
1212 1217 };
1213 1218 this.dirty = false;
1214 1219 this.select(0);
1215 1220 this.scroll_to_top();
1216 1221 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1217 1222 msg = "This notebook has been converted from an older " +
1218 1223 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1219 1224 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1220 1225 "newer notebook format will be used and older verions of IPython " +
1221 1226 "may not be able to read it. To keep the older version, close the " +
1222 1227 "notebook without saving it.";
1223 1228 var dialog = $('<div/>');
1224 1229 dialog.html(msg);
1225 1230 this.element.append(dialog);
1226 1231 dialog.dialog({
1227 1232 resizable: false,
1228 1233 modal: true,
1229 1234 title: "Notebook converted",
1230 1235 closeText: "",
1231 1236 close: function(event, ui) {$(this).dialog('destroy').remove();},
1232 1237 buttons : {
1233 1238 "OK": function () {
1234 1239 $(this).dialog('close');
1235 1240 }
1236 1241 },
1237 1242 width: 400
1238 1243 });
1239 1244 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1240 1245 var that = this;
1241 1246 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1242 1247 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1243 1248 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1244 1249 this_vs + ". You can still work with this notebook, but some features " +
1245 1250 "introduced in later notebook versions may not be available."
1246 1251
1247 1252 var dialog = $('<div/>');
1248 1253 dialog.html(msg);
1249 1254 this.element.append(dialog);
1250 1255 dialog.dialog({
1251 1256 resizable: false,
1252 1257 modal: true,
1253 1258 title: "Newer Notebook",
1254 1259 closeText: "",
1255 1260 close: function(event, ui) {$(this).dialog('destroy').remove();},
1256 1261 buttons : {
1257 1262 "OK": function () {
1258 1263 $(this).dialog('close');
1259 1264 }
1260 1265 },
1261 1266 width: 400
1262 1267 });
1263 1268
1264 1269 }
1265 1270 // Create the kernel after the notebook is completely loaded to prevent
1266 1271 // code execution upon loading, which is a security risk.
1267 1272 if (! this.read_only) {
1268 1273 this.start_kernel();
1269 1274 }
1270 1275 $([IPython.events]).trigger('notebook_loaded.Notebook');
1271 1276 };
1272 1277
1273 1278
1274 1279 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1275 1280 if (xhr.status === 500) {
1276 1281 msg = "An error occurred while loading this notebook. Most likely " +
1277 1282 "this notebook is in a newer format than is supported by this " +
1278 1283 "version of IPython. This version can load notebook formats " +
1279 1284 "v"+this.nbformat+" or earlier.";
1280 1285 var dialog = $('<div/>');
1281 1286 dialog.html(msg);
1282 1287 this.element.append(dialog);
1283 1288 dialog.dialog({
1284 1289 resizable: false,
1285 1290 modal: true,
1286 1291 title: "Error loading notebook",
1287 1292 closeText: "",
1288 1293 close: function(event, ui) {$(this).dialog('destroy').remove();},
1289 1294 buttons : {
1290 1295 "OK": function () {
1291 1296 $(this).dialog('close');
1292 1297 }
1293 1298 },
1294 1299 width: 400
1295 1300 });
1296 1301 }
1297 1302 }
1298 1303
1299 1304 IPython.Notebook = Notebook;
1300 1305
1301 1306
1302 1307 return IPython;
1303 1308
1304 1309 }(IPython));
1305 1310
General Comments 0
You need to be logged in to leave comments. Login now