##// END OF EJS Templates
Latexify formulas contained in html text....
Pablo Winant -
Show More
@@ -1,519 +1,519 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
14 14 var utils = IPython.utils;
15 15
16 16 var CodeCell = function (notebook) {
17 17 this.code_mirror = null;
18 18 this.input_prompt_number = ' ';
19 19 this.is_completing = false;
20 20 this.completion_cursor = null;
21 21 this.outputs = [];
22 22 this.collapsed = false;
23 23 IPython.Cell.apply(this, arguments);
24 24 };
25 25
26 26
27 27 CodeCell.prototype = new IPython.Cell();
28 28
29 29
30 30 CodeCell.prototype.create_element = function () {
31 31 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
32 32 cell.attr('tabindex','2');
33 33 var input = $('<div></div>').addClass('input hbox');
34 34 input.append($('<div/>').addClass('prompt input_prompt'));
35 35 var input_area = $('<div/>').addClass('input_area box-flex1');
36 36 this.code_mirror = CodeMirror(input_area.get(0), {
37 37 indentUnit : 4,
38 38 mode: 'python',
39 39 theme: 'ipython',
40 40 readOnly: this.read_only,
41 41 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
42 42 });
43 43 input.append(input_area);
44 44 var output = $('<div></div>').addClass('output vbox');
45 45 cell.append(input).append(output);
46 46 this.element = cell;
47 47 this.collapse()
48 48 };
49 49
50 50
51 51 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
52 52 // This method gets called in CodeMirror's onKeyDown/onKeyPress
53 53 // handlers and is used to provide custom key handling. Its return
54 54 // value is used to determine if CodeMirror should ignore the event:
55 55 // true = ignore, false = don't ignore.
56 56 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
57 57 // Always ignore shift-enter in CodeMirror as we handle it.
58 58 return true;
59 59 } else if (event.keyCode === 9 && event.type == 'keydown') {
60 60 // Tab completion.
61 61 var cur = editor.getCursor();
62 62 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur).trim();
63 63 if (pre_cursor === "") {
64 64 // Don't autocomplete if the part of the line before the cursor
65 65 // is empty. In this case, let CodeMirror handle indentation.
66 66 return false;
67 67 } else {
68 68 // Autocomplete the current line.
69 69 event.stop();
70 70 var line = editor.getLine(cur.line);
71 71 this.is_completing = true;
72 72 this.completion_cursor = cur;
73 73 IPython.notebook.complete_cell(this, line, cur.ch);
74 74 return true;
75 75 }
76 76 } else if (event.keyCode === 8 && event.type == 'keydown') {
77 77 // If backspace and the line ends with 4 spaces, remove them.
78 78 var cur = editor.getCursor();
79 79 var line = editor.getLine(cur.line);
80 80 var ending = line.slice(-4);
81 81 if (ending === ' ') {
82 82 editor.replaceRange('',
83 83 {line: cur.line, ch: cur.ch-4},
84 84 {line: cur.line, ch: cur.ch}
85 85 );
86 86 event.stop();
87 87 return true;
88 88 } else {
89 89 return false;
90 90 };
91 91 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
92 92 && event.type == 'keydown') {
93 93 // toggle line numbers with Ctrl-Shift-L
94 94 this.toggle_line_numbers();
95 95 }
96 96 else {
97 97 // keypress/keyup also trigger on TAB press, and we don't want to
98 98 // use those to disable tab completion.
99 99 if (this.is_completing && event.keyCode !== 9) {
100 100 var ed_cur = editor.getCursor();
101 101 var cc_cur = this.completion_cursor;
102 102 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
103 103 this.is_completing = false;
104 104 this.completion_cursor = null;
105 105 };
106 106 };
107 107 return false;
108 108 };
109 109 };
110 110
111 111
112 112 CodeCell.prototype.finish_completing = function (matched_text, matches) {
113 113 // console.log("Got matches", matched_text, matches);
114 114 if (!this.is_completing || matches.length === 0) {return;}
115 115
116 116 var that = this;
117 117 var cur = this.completion_cursor;
118 118
119 119 var insert = function (selected_text) {
120 120 that.code_mirror.replaceRange(
121 121 selected_text,
122 122 {line: cur.line, ch: (cur.ch-matched_text.length)},
123 123 {line: cur.line, ch: cur.ch}
124 124 );
125 125 };
126 126
127 127 if (matches.length === 1) {
128 128 insert(matches[0]);
129 129 setTimeout(function(){that.code_mirror.focus();}, 50);
130 130 return;
131 131 };
132 132
133 133 var complete = $('<div/>').addClass('completions');
134 134 var select = $('<select/>').attr('multiple','true');
135 135 for (var i=0; i<matches.length; ++i) {
136 136 select.append($('<option/>').text(matches[i]));
137 137 }
138 138 select.children().first().attr('selected','true');
139 139 select.attr('size',Math.min(10,matches.length));
140 140 var pos = this.code_mirror.cursorCoords();
141 141 complete.css('left',pos.x+'px');
142 142 complete.css('top',pos.yBot+'px');
143 143 complete.append(select);
144 144
145 145 $('body').append(complete);
146 146 var done = false;
147 147
148 148 var close = function () {
149 149 if (done) return;
150 150 done = true;
151 151 complete.remove();
152 152 that.is_completing = false;
153 153 that.completion_cursor = null;
154 154 };
155 155
156 156 var pick = function () {
157 157 insert(select.val()[0]);
158 158 close();
159 159 setTimeout(function(){that.code_mirror.focus();}, 50);
160 160 };
161 161
162 162 select.blur(close);
163 163 select.keydown(function (event) {
164 164 var code = event.which;
165 165 if (code === 13 || code === 32) {
166 166 // Pressing SPACE or ENTER will cause a pick
167 167 event.stopPropagation();
168 168 event.preventDefault();
169 169 pick();
170 170 } else if (code === 38 || code === 40) {
171 171 // We don't want the document keydown handler to handle UP/DOWN,
172 172 // but we want the default action.
173 173 event.stopPropagation();
174 174 } else {
175 175 // All other key presses exit completion.
176 176 event.stopPropagation();
177 177 event.preventDefault();
178 178 close();
179 179 that.code_mirror.focus();
180 180 }
181 181 });
182 182 // Double click also causes a pick.
183 183 select.dblclick(pick);
184 184 select.focus();
185 185 };
186 186
187 187 CodeCell.prototype.toggle_line_numbers = function () {
188 188 if (this.code_mirror.getOption('lineNumbers') == false) {
189 189 this.code_mirror.setOption('lineNumbers', true);
190 190 } else {
191 191 this.code_mirror.setOption('lineNumbers', false);
192 192 }
193 193 this.code_mirror.refresh()
194 194 };
195 195
196 196 CodeCell.prototype.select = function () {
197 197 IPython.Cell.prototype.select.apply(this);
198 198 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
199 199 // not causing the cursor to blink if the editor is empty initially.
200 200 // While this seems to fix the issue, this should be fixed
201 201 // in CodeMirror proper.
202 202 var s = this.code_mirror.getValue();
203 203 this.code_mirror.focus();
204 204 if (s === '') this.code_mirror.setValue('');
205 205 };
206 206
207 207
208 208 CodeCell.prototype.select_all = function () {
209 209 var start = {line: 0, ch: 0};
210 210 var nlines = this.code_mirror.lineCount();
211 211 var last_line = this.code_mirror.getLine(nlines-1);
212 212 var end = {line: nlines-1, ch: last_line.length};
213 213 this.code_mirror.setSelection(start, end);
214 214 };
215 215
216 216
217 217 CodeCell.prototype.append_output = function (json) {
218 218 this.expand();
219 219 if (json.output_type === 'pyout') {
220 220 this.append_pyout(json);
221 221 } else if (json.output_type === 'pyerr') {
222 222 this.append_pyerr(json);
223 223 } else if (json.output_type === 'display_data') {
224 224 this.append_display_data(json);
225 225 } else if (json.output_type === 'stream') {
226 226 this.append_stream(json);
227 227 };
228 228 this.outputs.push(json);
229 229 };
230 230
231 231
232 232 CodeCell.prototype.create_output_area = function () {
233 233 var oa = $("<div/>").addClass("hbox output_area");
234 234 oa.append($('<div/>').addClass('prompt'));
235 235 return oa;
236 236 };
237 237
238 238
239 239 CodeCell.prototype.append_pyout = function (json) {
240 240 n = json.prompt_number || ' ';
241 241 var toinsert = this.create_output_area();
242 242 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
243 243 this.append_mime_type(json, toinsert);
244 244 this.element.find('div.output').append(toinsert);
245 245 // If we just output latex, typeset it.
246 if (json.latex !== undefined) {
246 if ((json.latex !== undefined) || (json.html !== undefined)) {
247 247 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
248 248 };
249 249 };
250 250
251 251
252 252 CodeCell.prototype.append_pyerr = function (json) {
253 253 var tb = json.traceback;
254 254 if (tb !== undefined && tb.length > 0) {
255 255 var s = '';
256 256 var len = tb.length;
257 257 for (var i=0; i<len; i++) {
258 258 s = s + tb[i] + '\n';
259 259 }
260 260 s = s + '\n';
261 261 var toinsert = this.create_output_area();
262 262 this.append_text(s, toinsert);
263 263 this.element.find('div.output').append(toinsert);
264 264 };
265 265 };
266 266
267 267
268 268 CodeCell.prototype.append_stream = function (json) {
269 269 // temporary fix: if stream undefined (json file written prior to this patch),
270 270 // default to most likely stdout:
271 271 if (json.stream == undefined){
272 272 json.stream = 'stdout';
273 273 }
274 274 var subclass = "output_"+json.stream;
275 275 if (this.outputs.length > 0){
276 276 // have at least one output to consider
277 277 var last = this.outputs[this.outputs.length-1];
278 278 if (last.output_type == 'stream' && json.stream == last.stream){
279 279 // latest output was in the same stream,
280 280 // so append directly into its pre tag
281 281 this.element.find('div.'+subclass).last().find('pre').append(json.text);
282 282 return;
283 283 }
284 284 }
285 285
286 286 // If we got here, attach a new div
287 287 var toinsert = this.create_output_area();
288 288 this.append_text(json.text, toinsert, "output_stream "+subclass);
289 289 this.element.find('div.output').append(toinsert);
290 290 };
291 291
292 292
293 293 CodeCell.prototype.append_display_data = function (json) {
294 294 var toinsert = this.create_output_area();
295 295 this.append_mime_type(json, toinsert)
296 296 this.element.find('div.output').append(toinsert);
297 297 // If we just output latex, typeset it.
298 if (json.latex !== undefined) {
298 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
299 299 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
300 300 };
301 301 };
302 302
303 303
304 304 CodeCell.prototype.append_mime_type = function (json, element) {
305 305 if (json.html !== undefined) {
306 306 this.append_html(json.html, element);
307 307 } else if (json.latex !== undefined) {
308 308 this.append_latex(json.latex, element);
309 309 } else if (json.svg !== undefined) {
310 310 this.append_svg(json.svg, element);
311 311 } else if (json.png !== undefined) {
312 312 this.append_png(json.png, element);
313 313 } else if (json.jpeg !== undefined) {
314 314 this.append_jpeg(json.jpeg, element);
315 315 } else if (json.text !== undefined) {
316 316 this.append_text(json.text, element);
317 317 };
318 318 };
319 319
320 320
321 321 CodeCell.prototype.append_html = function (html, element) {
322 322 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
323 323 toinsert.append(html);
324 324 element.append(toinsert);
325 325 }
326 326
327 327
328 328 CodeCell.prototype.append_text = function (data, element, extra_class) {
329 329 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
330 330 if (extra_class){
331 331 toinsert.addClass(extra_class);
332 332 }
333 333 toinsert.append($("<pre/>").html(data));
334 334 element.append(toinsert);
335 335 };
336 336
337 337
338 338 CodeCell.prototype.append_svg = function (svg, element) {
339 339 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
340 340 toinsert.append(svg);
341 341 element.append(toinsert);
342 342 };
343 343
344 344
345 345 CodeCell.prototype.append_png = function (png, element) {
346 346 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
347 347 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
348 348 element.append(toinsert);
349 349 };
350 350
351 351
352 352 CodeCell.prototype.append_jpeg = function (jpeg, element) {
353 353 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
354 354 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
355 355 element.append(toinsert);
356 356 };
357 357
358 358
359 359 CodeCell.prototype.append_latex = function (latex, element) {
360 360 // This method cannot do the typesetting because the latex first has to
361 361 // be on the page.
362 362 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
363 363 toinsert.append(latex);
364 364 element.append(toinsert);
365 365 }
366 366
367 367
368 368 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
369 369 var output_div = this.element.find("div.output");
370 370 if (stdout && stderr && other){
371 371 // clear all, no need for logic
372 372 output_div.html("");
373 373 this.outputs = [];
374 374 return;
375 375 }
376 376 // remove html output
377 377 // each output_subarea that has an identifying class is in an output_area
378 378 // which is the element to be removed.
379 379 if (stdout){
380 380 output_div.find("div.output_stdout").parent().remove();
381 381 }
382 382 if (stderr){
383 383 output_div.find("div.output_stderr").parent().remove();
384 384 }
385 385 if (other){
386 386 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
387 387 }
388 388
389 389 // remove cleared outputs from JSON list:
390 390 for (var i = this.outputs.length - 1; i >= 0; i--){
391 391 var out = this.outputs[i];
392 392 var output_type = out.output_type;
393 393 if (output_type == "display_data" && other){
394 394 this.outputs.splice(i,1);
395 395 }else if (output_type == "stream"){
396 396 if (stdout && out.stream == "stdout"){
397 397 this.outputs.splice(i,1);
398 398 }else if (stderr && out.stream == "stderr"){
399 399 this.outputs.splice(i,1);
400 400 }
401 401 }
402 402 }
403 403 };
404 404
405 405
406 406 CodeCell.prototype.clear_input = function () {
407 407 this.code_mirror.setValue('');
408 408 };
409 409
410 410
411 411 CodeCell.prototype.collapse = function () {
412 412 if (!this.collapsed) {
413 413 this.element.find('div.output').hide();
414 414 this.collapsed = true;
415 415 };
416 416 };
417 417
418 418
419 419 CodeCell.prototype.expand = function () {
420 420 if (this.collapsed) {
421 421 this.element.find('div.output').show();
422 422 this.collapsed = false;
423 423 };
424 424 };
425 425
426 426
427 427 CodeCell.prototype.toggle_output = function () {
428 428 if (this.collapsed) {
429 429 this.expand();
430 430 } else {
431 431 this.collapse();
432 432 };
433 433 };
434 434
435 435 CodeCell.prototype.set_input_prompt = function (number) {
436 436 var n = number || ' ';
437 437 this.input_prompt_number = n
438 438 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
439 439 };
440 440
441 441
442 442 CodeCell.prototype.get_code = function () {
443 443 return this.code_mirror.getValue();
444 444 };
445 445
446 446
447 447 CodeCell.prototype.set_code = function (code) {
448 448 return this.code_mirror.setValue(code);
449 449 };
450 450
451 451
452 452 CodeCell.prototype.at_top = function () {
453 453 var cursor = this.code_mirror.getCursor();
454 454 if (cursor.line === 0) {
455 455 return true;
456 456 } else {
457 457 return false;
458 458 }
459 459 };
460 460
461 461
462 462 CodeCell.prototype.at_bottom = function () {
463 463 var cursor = this.code_mirror.getCursor();
464 464 if (cursor.line === (this.code_mirror.lineCount()-1)) {
465 465 return true;
466 466 } else {
467 467 return false;
468 468 }
469 469 };
470 470
471 471
472 472 CodeCell.prototype.fromJSON = function (data) {
473 473 console.log('Import from JSON:', data);
474 474 if (data.cell_type === 'code') {
475 475 if (data.input !== undefined) {
476 476 this.set_code(data.input);
477 477 }
478 478 if (data.prompt_number !== undefined) {
479 479 this.set_input_prompt(data.prompt_number);
480 480 } else {
481 481 this.set_input_prompt();
482 482 };
483 483 var len = data.outputs.length;
484 484 for (var i=0; i<len; i++) {
485 485 this.append_output(data.outputs[i]);
486 486 };
487 487 if (data.collapsed !== undefined) {
488 488 if (data.collapsed) {
489 489 this.collapse();
490 490 };
491 491 };
492 492 };
493 493 };
494 494
495 495
496 496 CodeCell.prototype.toJSON = function () {
497 497 var data = {};
498 498 data.input = this.get_code();
499 499 data.cell_type = 'code';
500 500 if (this.input_prompt_number !== ' ') {
501 501 data.prompt_number = this.input_prompt_number
502 502 };
503 503 var outputs = [];
504 504 var len = this.outputs.length;
505 505 for (var i=0; i<len; i++) {
506 506 outputs[i] = this.outputs[i];
507 507 };
508 508 data.outputs = outputs;
509 509 data.language = 'python';
510 510 data.collapsed = this.collapsed;
511 511 // console.log('Export to JSON:',data);
512 512 return data;
513 513 };
514 514
515 515
516 516 IPython.CodeCell = CodeCell;
517 517
518 518 return IPython;
519 519 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now