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