##// END OF EJS Templates
Fixing latex rendering bug.
Brian E. Granger -
Show More
@@ -1,422 +1,420 b''
1 1
2 2 //============================================================================
3 3 // CodeCell
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var utils = IPython.utils;
9 9
10 10 var CodeCell = function (notebook) {
11 11 this.code_mirror = null;
12 12 this.input_prompt_number = ' ';
13 13 this.is_completing = false;
14 14 this.completion_cursor = null;
15 15 this.outputs = [];
16 16 this.collapsed = false;
17 17 IPython.Cell.apply(this, arguments);
18 18 };
19 19
20 20
21 21 CodeCell.prototype = new IPython.Cell();
22 22
23 23
24 24 CodeCell.prototype.create_element = function () {
25 25 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
26 26 var input = $('<div></div>').addClass('input hbox');
27 27 input.append($('<div/>').addClass('prompt input_prompt'));
28 28 var input_area = $('<div/>').addClass('input_area box-flex1');
29 29 this.code_mirror = CodeMirror(input_area.get(0), {
30 30 indentUnit : 4,
31 31 mode: 'python',
32 32 theme: 'ipython',
33 33 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
34 34 });
35 35 input.append(input_area);
36 36 var output = $('<div></div>').addClass('output vbox');
37 37 cell.append(input).append(output);
38 38 this.element = cell;
39 39 this.collapse()
40 40 };
41 41
42 42
43 43 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
44 44 // This method gets called in CodeMirror's onKeyDown/onKeyPress handlers and
45 45 // is used to provide custom key handling. Its return value is used to determine
46 46 // if CodeMirror should ignore the event: true = ignore, false = don't ignore.
47 47 if (event.keyCode === 13 && event.shiftKey) {
48 48 // Always ignore shift-enter in CodeMirror as we handle it.
49 49 return true;
50 50 } else if (event.keyCode === 9) {
51 51 var cur = editor.getCursor();
52 52 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur).trim();
53 53 if (pre_cursor === "") {
54 54 // Don't autocomplete if the part of the line before the cursor is empty.
55 55 // In this case, let CodeMirror handle indentation.
56 56 return false;
57 57 } else {
58 58 // Autocomplete the current line.
59 59 event.stop();
60 60 var line = editor.getLine(cur.line);
61 61 this.is_completing = true;
62 62 this.completion_cursor = cur;
63 63 IPython.notebook.complete_cell(this, line, cur.ch);
64 64 return true;
65 65 }
66 66 } else if (event.keyCode === 8 && event.type == 'keydown') {
67 67 // If backspace and the line ends with 4 spaces, remove them.
68 68 var cur = editor.getCursor();
69 69 var line = editor.getLine(cur.line);
70 70 var ending = line.slice(-4);
71 71 if (ending === ' ') {
72 72 editor.replaceRange('',
73 73 {line: cur.line, ch: cur.ch-4},
74 74 {line: cur.line, ch: cur.ch}
75 75 );
76 76 event.stop();
77 77 return true;
78 78 } else {
79 79 return false;
80 80 };
81 81 } else {
82 82 if (this.is_completing && this.completion_cursor !== editor.getCursor()) {
83 83 this.is_completing = false;
84 84 this.completion_cursor = null;
85 85 }
86 86 return false;
87 87 };
88 88 };
89 89
90 90
91 91 CodeCell.prototype.finish_completing = function (matched_text, matches) {
92 92 if (!this.is_completing || matches.length === 0) {return;}
93 93 // console.log("Got matches", matched_text, matches);
94 94
95 95 var that = this;
96 96 var cur = this.completion_cursor;
97 97 var complete = $('<div/>').addClass('completions');
98 98 var select = $('<select/>').attr('multiple','true');
99 99 for (var i=0; i<matches.length; ++i) {
100 100 select.append($('<option/>').text(matches[i]));
101 101 }
102 102 select.children().first().attr('selected','true');
103 103 select.attr('size',Math.min(10,matches.length));
104 104 var pos = this.code_mirror.cursorCoords();
105 105 complete.css('left',pos.x+'px');
106 106 complete.css('top',pos.yBot+'px');
107 107 complete.append(select);
108 108
109 109 $('body').append(complete);
110 110 var done = false;
111 111
112 112 var insert = function (selected_text) {
113 113 that.code_mirror.replaceRange(
114 114 selected_text,
115 115 {line: cur.line, ch: (cur.ch-matched_text.length)},
116 116 {line: cur.line, ch: cur.ch}
117 117 );
118 118 };
119 119
120 120 var close = function () {
121 121 if (done) return;
122 122 done = true;
123 123 complete.remove();
124 124 that.is_completing = false;
125 125 that.completion_cursor = null;
126 126 };
127 127
128 128 var pick = function () {
129 129 insert(select.val()[0]);
130 130 close();
131 131 setTimeout(function(){that.code_mirror.focus();}, 50);
132 132 };
133 133
134 134 select.blur(close);
135 135 select.keydown(function (event) {
136 136 var code = event.which;
137 137 if (code === 13 || code === 32) {
138 138 // Pressing SPACE or ENTER will cause a pick
139 139 event.stopPropagation();
140 140 event.preventDefault();
141 141 pick();
142 142 } else if (code === 38 || code === 40) {
143 143 // We don't want the document keydown handler to handle UP/DOWN,
144 144 // but we want the default action.
145 145 event.stopPropagation();
146 146 } else {
147 147 // All other key presses simple exit completion.
148 148 event.stopPropagation();
149 149 event.preventDefault();
150 150 close();
151 151 that.code_mirror.focus();
152 152 }
153 153 });
154 154 // Double click also causes a pick.
155 155 select.dblclick(pick);
156 156 select.focus();
157 157 };
158 158
159 159
160 160 CodeCell.prototype.select = function () {
161 161 IPython.Cell.prototype.select.apply(this);
162 162 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
163 163 // not causing the cursor to blink if the editor is empty initially.
164 164 // While this seems to fix the issue, this should be fixed
165 165 // in CodeMirror proper.
166 166 var s = this.code_mirror.getValue();
167 167 if (s === '') this.code_mirror.setValue('.');
168 168 this.code_mirror.focus();
169 169 if (s === '') this.code_mirror.setValue('');
170 170 };
171 171
172 172
173 173 CodeCell.prototype.append_output = function (json) {
174 174 this.expand();
175 175 if (json.output_type === 'pyout') {
176 176 this.append_pyout(json);
177 177 } else if (json.output_type === 'pyerr') {
178 178 this.append_pyerr(json);
179 179 } else if (json.output_type === 'display_data') {
180 180 this.append_display_data(json);
181 181 } else if (json.output_type === 'stream') {
182 182 this.append_stream(json);
183 183 };
184 184 this.outputs.push(json);
185 185 };
186 186
187 187
188 188 CodeCell.prototype.append_pyout = function (json) {
189 189 n = json.prompt_number || ' ';
190 190 var toinsert = $("<div/>").addClass("output_pyout hbox");
191 191 toinsert.append($('<div/>').
192 192 addClass('prompt output_prompt').
193 193 html('Out[' + n + ']:')
194 194 );
195 195 this.append_mime_type(json, toinsert).addClass('output_area');
196 196 toinsert.children().last().addClass("box_flex1 pyout_area");
197 197 this.element.find("div.output").append(toinsert);
198 198 // If we just output latex, typeset it.
199 199 if (json.latex !== undefined) {
200 200 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
201 201 };
202 202 };
203 203
204 204
205 205 CodeCell.prototype.append_pyerr = function (json) {
206 206 var tb = json.traceback;
207 207 if (tb !== undefined && tb.length > 0) {
208 208 var s = '';
209 209 var len = tb.length;
210 210 for (var i=0; i<len; i++) {
211 211 s = s + tb[i] + '\n';
212 212 }
213 213 s = s + '\n';
214 214 this.append_text(s).addClass('output_area');
215 215 };
216 216 };
217 217
218 218
219 219 CodeCell.prototype.append_stream = function (json) {
220 220 this.append_text(json.text).addClass('output_area');
221 221 };
222 222
223 223
224 224 CodeCell.prototype.append_display_data = function (json) {
225 225 this.append_mime_type(json).addClass('output_area');
226 // If we just output latex, typeset it.
227 if (json.latex !== undefined) {
228 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
229 };
226 230 };
227 231
228 232
229 233 CodeCell.prototype.append_mime_type = function (json, element) {
230 234 element = element || this.element.find("div.output");
231 235 if (json.html !== undefined) {
232 236 this.append_html(json.html, element);
233 237 } else if (json.latex !== undefined) {
234 238 this.append_latex(json.latex, element);
235 // If it is undefined, then we just appended to div.output, which
236 // makes the latex visible and we can typeset it. The typesetting
237 // has to be done after the latex is on the page.
238 if (element === undefined) {
239 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
240 };
241 239 } else if (json.svg !== undefined) {
242 240 this.append_svg(json.svg, element);
243 241 } else if (json.png !== undefined) {
244 242 this.append_png(json.png, element);
245 243 } else if (json.jpeg !== undefined) {
246 244 this.append_jpeg(json.jpeg, element);
247 245 } else if (json.text !== undefined) {
248 246 this.append_text(json.text, element);
249 247 };
250 248 return element;
251 249 };
252 250
253 251
254 252 CodeCell.prototype.append_html = function (html, element) {
255 253 element = element || this.element.find("div.output");
256 254 var toinsert = $("<div/>").addClass("output_html rendered_html");
257 255 toinsert.append(html);
258 256 element.append(toinsert);
259 257 return element;
260 258 }
261 259
262 260
263 261 CodeCell.prototype.append_text = function (data, element) {
264 262 element = element || this.element.find("div.output");
265 263 var toinsert = $("<div/>").addClass("output_stream");
266 264 toinsert.append($("<pre/>").html(data));
267 265 element.append(toinsert);
268 266 return element;
269 267 };
270 268
271 269
272 270 CodeCell.prototype.append_svg = function (svg, element) {
273 271 element = element || this.element.find("div.output");
274 272 var toinsert = $("<div/>").addClass("output_svg");
275 273 toinsert.append(svg);
276 274 element.append(toinsert);
277 275 return element;
278 276 };
279 277
280 278
281 279 CodeCell.prototype.append_png = function (png, element) {
282 280 element = element || this.element.find("div.output");
283 281 var toinsert = $("<div/>").addClass("output_png");
284 282 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
285 283 element.append(toinsert);
286 284 return element;
287 285 };
288 286
289 287
290 288 CodeCell.prototype.append_jpeg = function (jpeg, element) {
291 289 element = element || this.element.find("div.output");
292 290 var toinsert = $("<div/>").addClass("output_jpeg");
293 291 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
294 292 element.append(toinsert);
295 293 return element;
296 294 };
297 295
298 296
299 297 CodeCell.prototype.append_latex = function (latex, element) {
300 298 // This method cannot do the typesetting because the latex first has to
301 299 // be on the page.
302 300 element = element || this.element.find("div.output");
303 301 var toinsert = $("<div/>").addClass("output_latex");
304 302 toinsert.append(latex);
305 303 element.append(toinsert);
306 304 return element;
307 305 }
308 306
309 307
310 308 CodeCell.prototype.clear_output = function () {
311 309 this.element.find("div.output").html("");
312 310 this.outputs = [];
313 311 };
314 312
315 313
316 314 CodeCell.prototype.clear_input = function () {
317 315 this.code_mirror.setValue('');
318 316 };
319 317
320 318
321 319 CodeCell.prototype.collapse = function () {
322 320 if (!this.collapsed) {
323 321 this.element.find('div.output').hide();
324 322 this.collapsed = true;
325 323 };
326 324 };
327 325
328 326
329 327 CodeCell.prototype.expand = function () {
330 328 if (this.collapsed) {
331 329 this.element.find('div.output').show();
332 330 this.collapsed = false;
333 331 };
334 332 };
335 333
336 334
337 335 CodeCell.prototype.set_input_prompt = function (number) {
338 336 var n = number || ' ';
339 337 this.input_prompt_number = n
340 338 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
341 339 };
342 340
343 341
344 342 CodeCell.prototype.get_code = function () {
345 343 return this.code_mirror.getValue();
346 344 };
347 345
348 346
349 347 CodeCell.prototype.set_code = function (code) {
350 348 return this.code_mirror.setValue(code);
351 349 };
352 350
353 351
354 352 CodeCell.prototype.at_top = function () {
355 353 var cursor = this.code_mirror.getCursor();
356 354 if (cursor.line === 0) {
357 355 return true;
358 356 } else {
359 357 return false;
360 358 }
361 359 };
362 360
363 361
364 362 CodeCell.prototype.at_bottom = function () {
365 363 var cursor = this.code_mirror.getCursor();
366 364 if (cursor.line === (this.code_mirror.lineCount()-1)) {
367 365 return true;
368 366 } else {
369 367 return false;
370 368 }
371 369 };
372 370
373 371
374 372 CodeCell.prototype.fromJSON = function (data) {
375 373 // console.log('Import from JSON:', data);
376 374 if (data.cell_type === 'code') {
377 375 if (data.input !== undefined) {
378 376 this.set_code(data.input);
379 377 }
380 378 if (data.prompt_number !== undefined) {
381 379 this.set_input_prompt(data.prompt_number);
382 380 } else {
383 381 this.set_input_prompt();
384 382 };
385 383 var len = data.outputs.length;
386 384 for (var i=0; i<len; i++) {
387 385 this.append_output(data.outputs[i]);
388 386 };
389 387 if (data.collapsed !== undefined) {
390 388 if (data.collapsed) {
391 389 this.collapse();
392 390 };
393 391 };
394 392 };
395 393 };
396 394
397 395
398 396 CodeCell.prototype.toJSON = function () {
399 397 var data = {};
400 398 data.input = this.get_code();
401 399 data.cell_type = 'code';
402 400 if (this.input_prompt_number !== ' ') {
403 401 data.prompt_number = this.input_prompt_number
404 402 };
405 403 var outputs = [];
406 404 var len = this.outputs.length;
407 405 for (var i=0; i<len; i++) {
408 406 outputs[i] = this.outputs[i];
409 407 };
410 408 data.outputs = outputs;
411 409 data.language = 'python';
412 410 data.collapsed = this.collapsed;
413 411 // console.log('Export to JSON:',data);
414 412 return data;
415 413 };
416 414
417 415
418 416 IPython.CodeCell = CodeCell;
419 417
420 418 return IPython;
421 419 }(IPython));
422 420
General Comments 0
You need to be logged in to leave comments. Login now