##// END OF EJS Templates
Merge codemirror config with user config....
Matthias Bussonnier -
Show More
@@ -1,707 +1,711 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 /**
4 /**
5 *
5 *
6 *
6 *
7 * @module cell
7 * @module cell
8 * @namespace cell
8 * @namespace cell
9 * @class Cell
9 * @class Cell
10 */
10 */
11
11
12
12
13 define([
13 define([
14 'base/js/namespace',
14 'base/js/namespace',
15 'jquery',
15 'jquery',
16 'base/js/utils',
16 'base/js/utils',
17 'codemirror/lib/codemirror',
17 'codemirror/lib/codemirror',
18 'codemirror/addon/edit/matchbrackets',
18 'codemirror/addon/edit/matchbrackets',
19 'codemirror/addon/edit/closebrackets',
19 'codemirror/addon/edit/closebrackets',
20 'codemirror/addon/comment/comment'
20 'codemirror/addon/comment/comment'
21 ], function(IPython, $, utils, CodeMirror, cm_match, cm_closeb, cm_comment) {
21 ], function(IPython, $, utils, CodeMirror, cm_match, cm_closeb, cm_comment) {
22 // TODO: remove IPython dependency here
22 // TODO: remove IPython dependency here
23 "use strict";
23 "use strict";
24
24
25 var overlayHack = CodeMirror.scrollbarModel.native.prototype.overlayHack;
25 var overlayHack = CodeMirror.scrollbarModel.native.prototype.overlayHack;
26
26
27 CodeMirror.scrollbarModel.native.prototype.overlayHack = function () {
27 CodeMirror.scrollbarModel.native.prototype.overlayHack = function () {
28 overlayHack.apply(this, arguments);
28 overlayHack.apply(this, arguments);
29 // Reverse `min-height: 18px` scrollbar hack on OS X
29 // Reverse `min-height: 18px` scrollbar hack on OS X
30 // which causes a dead area, making it impossible to click on the last line
30 // which causes a dead area, making it impossible to click on the last line
31 // when there is horizontal scrolling to do and the "show scrollbar only when scrolling" behavior
31 // when there is horizontal scrolling to do and the "show scrollbar only when scrolling" behavior
32 // is enabled.
32 // is enabled.
33 // This, in turn, has the undesirable behavior of never showing the horizontal scrollbar,
33 // This, in turn, has the undesirable behavior of never showing the horizontal scrollbar,
34 // even when it should, which is less problematic, at least.
34 // even when it should, which is less problematic, at least.
35 if (/Mac/.test(navigator.platform)) {
35 if (/Mac/.test(navigator.platform)) {
36 this.horiz.style.minHeight = "";
36 this.horiz.style.minHeight = "";
37 }
37 }
38 };
38 };
39
39
40 var Cell = function (options) {
40 var Cell = function (options) {
41 /* Constructor
41 /* Constructor
42 *
42 *
43 * The Base `Cell` class from which to inherit.
43 * The Base `Cell` class from which to inherit.
44 * @constructor
44 * @constructor
45 * @param:
45 * @param:
46 * options: dictionary
46 * options: dictionary
47 * Dictionary of keyword arguments.
47 * Dictionary of keyword arguments.
48 * events: $(Events) instance
48 * events: $(Events) instance
49 * config: dictionary
49 * config: dictionary
50 * keyboard_manager: KeyboardManager instance
50 * keyboard_manager: KeyboardManager instance
51 */
51 */
52 options = options || {};
52 options = options || {};
53 this.keyboard_manager = options.keyboard_manager;
53 this.keyboard_manager = options.keyboard_manager;
54 this.events = options.events;
54 this.events = options.events;
55 var config = utils.mergeopt(Cell, options.config);
55 var config = utils.mergeopt(Cell, options.config);
56 // superclass default overwrite our default
56 // superclass default overwrite our default
57
57
58 this.placeholder = config.placeholder || '';
58 this.placeholder = config.placeholder || '';
59 this.read_only = config.cm_config.readOnly;
60 this.selected = false;
59 this.selected = false;
61 this.rendered = false;
60 this.rendered = false;
62 this.mode = 'command';
61 this.mode = 'command';
63
62
64 // Metadata property
63 // Metadata property
65 var that = this;
64 var that = this;
66 this._metadata = {};
65 this._metadata = {};
67 Object.defineProperty(this, 'metadata', {
66 Object.defineProperty(this, 'metadata', {
68 get: function() { return that._metadata; },
67 get: function() { return that._metadata; },
69 set: function(value) {
68 set: function(value) {
70 that._metadata = value;
69 that._metadata = value;
71 if (that.celltoolbar) {
70 if (that.celltoolbar) {
72 that.celltoolbar.rebuild();
71 that.celltoolbar.rebuild();
73 }
72 }
74 }
73 }
75 });
74 });
76
75
77 // load this from metadata later ?
76 // load this from metadata later ?
78 this.user_highlight = 'auto';
77 this.user_highlight = 'auto';
79 this.cm_config = config.cm_config;
78
79 var class_conf_value = {};
80 if(this.class_config){
81 class_conf_value = this.class_config.get_sync('cm_config');
82 }
83 this.cm_config = utils.mergeopt({}, config.cm_config, class_conf_value);
80 this.cell_id = utils.uuid();
84 this.cell_id = utils.uuid();
81 this._options = config;
85 this._options = config;
82
86
83 // For JS VM engines optimization, attributes should be all set (even
87 // For JS VM engines optimization, attributes should be all set (even
84 // to null) in the constructor, and if possible, if different subclass
88 // to null) in the constructor, and if possible, if different subclass
85 // have new attributes with same name, they should be created in the
89 // have new attributes with same name, they should be created in the
86 // same order. Easiest is to create and set to null in parent class.
90 // same order. Easiest is to create and set to null in parent class.
87
91
88 this.element = null;
92 this.element = null;
89 this.cell_type = this.cell_type || null;
93 this.cell_type = this.cell_type || null;
90 this.code_mirror = null;
94 this.code_mirror = null;
91
95
92 this.create_element();
96 this.create_element();
93 if (this.element !== null) {
97 if (this.element !== null) {
94 this.element.data("cell", this);
98 this.element.data("cell", this);
95 this.bind_events();
99 this.bind_events();
96 this.init_classes();
100 this.init_classes();
97 }
101 }
98 };
102 };
99
103
100 Cell.options_default = {
104 Cell.options_default = {
101 cm_config : {
105 cm_config : {
102 indentUnit : 4,
106 indentUnit : 4,
103 readOnly: false,
107 readOnly: false,
104 theme: "default",
108 theme: "default",
105 extraKeys: {
109 extraKeys: {
106 "Cmd-Right":"goLineRight",
110 "Cmd-Right":"goLineRight",
107 "End":"goLineRight",
111 "End":"goLineRight",
108 "Cmd-Left":"goLineLeft"
112 "Cmd-Left":"goLineLeft"
109 }
113 }
110 }
114 }
111 };
115 };
112
116
113 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
117 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
114 // by disabling drag/drop altogether on Safari
118 // by disabling drag/drop altogether on Safari
115 // https://github.com/codemirror/CodeMirror/issues/332
119 // https://github.com/codemirror/CodeMirror/issues/332
116 if (utils.browser[0] == "Safari") {
120 if (utils.browser[0] == "Safari") {
117 Cell.options_default.cm_config.dragDrop = false;
121 Cell.options_default.cm_config.dragDrop = false;
118 }
122 }
119
123
120 /**
124 /**
121 * Empty. Subclasses must implement create_element.
125 * Empty. Subclasses must implement create_element.
122 * This should contain all the code to create the DOM element in notebook
126 * This should contain all the code to create the DOM element in notebook
123 * and will be called by Base Class constructor.
127 * and will be called by Base Class constructor.
124 * @method create_element
128 * @method create_element
125 */
129 */
126 Cell.prototype.create_element = function () {
130 Cell.prototype.create_element = function () {
127 };
131 };
128
132
129 Cell.prototype.init_classes = function () {
133 Cell.prototype.init_classes = function () {
130 /**
134 /**
131 * Call after this.element exists to initialize the css classes
135 * Call after this.element exists to initialize the css classes
132 * related to selected, rendered and mode.
136 * related to selected, rendered and mode.
133 */
137 */
134 if (this.selected) {
138 if (this.selected) {
135 this.element.addClass('selected');
139 this.element.addClass('selected');
136 } else {
140 } else {
137 this.element.addClass('unselected');
141 this.element.addClass('unselected');
138 }
142 }
139 if (this.rendered) {
143 if (this.rendered) {
140 this.element.addClass('rendered');
144 this.element.addClass('rendered');
141 } else {
145 } else {
142 this.element.addClass('unrendered');
146 this.element.addClass('unrendered');
143 }
147 }
144 };
148 };
145
149
146 /**
150 /**
147 * Subclasses can implement override bind_events.
151 * Subclasses can implement override bind_events.
148 * Be carefull to call the parent method when overwriting as it fires event.
152 * Be carefull to call the parent method when overwriting as it fires event.
149 * this will be triggerd after create_element in constructor.
153 * this will be triggerd after create_element in constructor.
150 * @method bind_events
154 * @method bind_events
151 */
155 */
152 Cell.prototype.bind_events = function () {
156 Cell.prototype.bind_events = function () {
153 var that = this;
157 var that = this;
154 // We trigger events so that Cell doesn't have to depend on Notebook.
158 // We trigger events so that Cell doesn't have to depend on Notebook.
155 that.element.click(function (event) {
159 that.element.click(function (event) {
156 if (!that.selected) {
160 if (!that.selected) {
157 that.events.trigger('select.Cell', {'cell':that});
161 that.events.trigger('select.Cell', {'cell':that});
158 }
162 }
159 });
163 });
160 that.element.focusin(function (event) {
164 that.element.focusin(function (event) {
161 if (!that.selected) {
165 if (!that.selected) {
162 that.events.trigger('select.Cell', {'cell':that});
166 that.events.trigger('select.Cell', {'cell':that});
163 }
167 }
164 });
168 });
165 if (this.code_mirror) {
169 if (this.code_mirror) {
166 this.code_mirror.on("change", function(cm, change) {
170 this.code_mirror.on("change", function(cm, change) {
167 that.events.trigger("set_dirty.Notebook", {value: true});
171 that.events.trigger("set_dirty.Notebook", {value: true});
168 });
172 });
169 }
173 }
170 if (this.code_mirror) {
174 if (this.code_mirror) {
171 this.code_mirror.on('focus', function(cm, change) {
175 this.code_mirror.on('focus', function(cm, change) {
172 that.events.trigger('edit_mode.Cell', {cell: that});
176 that.events.trigger('edit_mode.Cell', {cell: that});
173 });
177 });
174 }
178 }
175 if (this.code_mirror) {
179 if (this.code_mirror) {
176 this.code_mirror.on('blur', function(cm, change) {
180 this.code_mirror.on('blur', function(cm, change) {
177 that.events.trigger('command_mode.Cell', {cell: that});
181 that.events.trigger('command_mode.Cell', {cell: that});
178 });
182 });
179 }
183 }
180
184
181 this.element.dblclick(function () {
185 this.element.dblclick(function () {
182 if (that.selected === false) {
186 if (that.selected === false) {
183 this.events.trigger('select.Cell', {'cell':that});
187 this.events.trigger('select.Cell', {'cell':that});
184 }
188 }
185 var cont = that.unrender();
189 var cont = that.unrender();
186 if (cont) {
190 if (cont) {
187 that.focus_editor();
191 that.focus_editor();
188 }
192 }
189 });
193 });
190 };
194 };
191
195
192 /**
196 /**
193 * This method gets called in CodeMirror's onKeyDown/onKeyPress
197 * This method gets called in CodeMirror's onKeyDown/onKeyPress
194 * handlers and is used to provide custom key handling.
198 * handlers and is used to provide custom key handling.
195 *
199 *
196 * To have custom handling, subclasses should override this method, but still call it
200 * To have custom handling, subclasses should override this method, but still call it
197 * in order to process the Edit mode keyboard shortcuts.
201 * in order to process the Edit mode keyboard shortcuts.
198 *
202 *
199 * @method handle_codemirror_keyevent
203 * @method handle_codemirror_keyevent
200 * @param {CodeMirror} editor - The codemirror instance bound to the cell
204 * @param {CodeMirror} editor - The codemirror instance bound to the cell
201 * @param {event} event - key press event which either should or should not be handled by CodeMirror
205 * @param {event} event - key press event which either should or should not be handled by CodeMirror
202 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
206 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
203 */
207 */
204 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
208 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
205 var shortcuts = this.keyboard_manager.edit_shortcuts;
209 var shortcuts = this.keyboard_manager.edit_shortcuts;
206
210
207 var cur = editor.getCursor();
211 var cur = editor.getCursor();
208 if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){
212 if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){
209 event._ipkmIgnore = true;
213 event._ipkmIgnore = true;
210 }
214 }
211 var nLastLine = editor.lastLine();
215 var nLastLine = editor.lastLine();
212 if ((event.keyCode === 40) &&
216 if ((event.keyCode === 40) &&
213 ((cur.line !== nLastLine) ||
217 ((cur.line !== nLastLine) ||
214 (cur.ch !== editor.getLineHandle(nLastLine).text.length))
218 (cur.ch !== editor.getLineHandle(nLastLine).text.length))
215 ) {
219 ) {
216 event._ipkmIgnore = true;
220 event._ipkmIgnore = true;
217 }
221 }
218 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
222 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
219 // manager will handle it
223 // manager will handle it
220 if (shortcuts.handles(event)) {
224 if (shortcuts.handles(event)) {
221 return true;
225 return true;
222 }
226 }
223
227
224 return false;
228 return false;
225 };
229 };
226
230
227
231
228 /**
232 /**
229 * Triger typsetting of math by mathjax on current cell element
233 * Triger typsetting of math by mathjax on current cell element
230 * @method typeset
234 * @method typeset
231 */
235 */
232 Cell.prototype.typeset = function () {
236 Cell.prototype.typeset = function () {
233 utils.typeset(this.element);
237 utils.typeset(this.element);
234 };
238 };
235
239
236 /**
240 /**
237 * handle cell level logic when a cell is selected
241 * handle cell level logic when a cell is selected
238 * @method select
242 * @method select
239 * @return is the action being taken
243 * @return is the action being taken
240 */
244 */
241 Cell.prototype.select = function () {
245 Cell.prototype.select = function () {
242 if (!this.selected) {
246 if (!this.selected) {
243 this.element.addClass('selected');
247 this.element.addClass('selected');
244 this.element.removeClass('unselected');
248 this.element.removeClass('unselected');
245 this.selected = true;
249 this.selected = true;
246 return true;
250 return true;
247 } else {
251 } else {
248 return false;
252 return false;
249 }
253 }
250 };
254 };
251
255
252 /**
256 /**
253 * handle cell level logic when a cell is unselected
257 * handle cell level logic when a cell is unselected
254 * @method unselect
258 * @method unselect
255 * @return is the action being taken
259 * @return is the action being taken
256 */
260 */
257 Cell.prototype.unselect = function () {
261 Cell.prototype.unselect = function () {
258 if (this.selected) {
262 if (this.selected) {
259 this.element.addClass('unselected');
263 this.element.addClass('unselected');
260 this.element.removeClass('selected');
264 this.element.removeClass('selected');
261 this.selected = false;
265 this.selected = false;
262 return true;
266 return true;
263 } else {
267 } else {
264 return false;
268 return false;
265 }
269 }
266 };
270 };
267
271
268 /**
272 /**
269 * should be overritten by subclass
273 * should be overritten by subclass
270 * @method execute
274 * @method execute
271 */
275 */
272 Cell.prototype.execute = function () {
276 Cell.prototype.execute = function () {
273 return;
277 return;
274 };
278 };
275
279
276 /**
280 /**
277 * handle cell level logic when a cell is rendered
281 * handle cell level logic when a cell is rendered
278 * @method render
282 * @method render
279 * @return is the action being taken
283 * @return is the action being taken
280 */
284 */
281 Cell.prototype.render = function () {
285 Cell.prototype.render = function () {
282 if (!this.rendered) {
286 if (!this.rendered) {
283 this.element.addClass('rendered');
287 this.element.addClass('rendered');
284 this.element.removeClass('unrendered');
288 this.element.removeClass('unrendered');
285 this.rendered = true;
289 this.rendered = true;
286 return true;
290 return true;
287 } else {
291 } else {
288 return false;
292 return false;
289 }
293 }
290 };
294 };
291
295
292 /**
296 /**
293 * handle cell level logic when a cell is unrendered
297 * handle cell level logic when a cell is unrendered
294 * @method unrender
298 * @method unrender
295 * @return is the action being taken
299 * @return is the action being taken
296 */
300 */
297 Cell.prototype.unrender = function () {
301 Cell.prototype.unrender = function () {
298 if (this.rendered) {
302 if (this.rendered) {
299 this.element.addClass('unrendered');
303 this.element.addClass('unrendered');
300 this.element.removeClass('rendered');
304 this.element.removeClass('rendered');
301 this.rendered = false;
305 this.rendered = false;
302 return true;
306 return true;
303 } else {
307 } else {
304 return false;
308 return false;
305 }
309 }
306 };
310 };
307
311
308 /**
312 /**
309 * Delegates keyboard shortcut handling to either IPython keyboard
313 * Delegates keyboard shortcut handling to either IPython keyboard
310 * manager when in command mode, or CodeMirror when in edit mode
314 * manager when in command mode, or CodeMirror when in edit mode
311 *
315 *
312 * @method handle_keyevent
316 * @method handle_keyevent
313 * @param {CodeMirror} editor - The codemirror instance bound to the cell
317 * @param {CodeMirror} editor - The codemirror instance bound to the cell
314 * @param {event} - key event to be handled
318 * @param {event} - key event to be handled
315 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
319 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
316 */
320 */
317 Cell.prototype.handle_keyevent = function (editor, event) {
321 Cell.prototype.handle_keyevent = function (editor, event) {
318 if (this.mode === 'command') {
322 if (this.mode === 'command') {
319 return true;
323 return true;
320 } else if (this.mode === 'edit') {
324 } else if (this.mode === 'edit') {
321 return this.handle_codemirror_keyevent(editor, event);
325 return this.handle_codemirror_keyevent(editor, event);
322 }
326 }
323 };
327 };
324
328
325 /**
329 /**
326 * @method at_top
330 * @method at_top
327 * @return {Boolean}
331 * @return {Boolean}
328 */
332 */
329 Cell.prototype.at_top = function () {
333 Cell.prototype.at_top = function () {
330 var cm = this.code_mirror;
334 var cm = this.code_mirror;
331 var cursor = cm.getCursor();
335 var cursor = cm.getCursor();
332 if (cursor.line === 0 && cursor.ch === 0) {
336 if (cursor.line === 0 && cursor.ch === 0) {
333 return true;
337 return true;
334 }
338 }
335 return false;
339 return false;
336 };
340 };
337
341
338 /**
342 /**
339 * @method at_bottom
343 * @method at_bottom
340 * @return {Boolean}
344 * @return {Boolean}
341 * */
345 * */
342 Cell.prototype.at_bottom = function () {
346 Cell.prototype.at_bottom = function () {
343 var cm = this.code_mirror;
347 var cm = this.code_mirror;
344 var cursor = cm.getCursor();
348 var cursor = cm.getCursor();
345 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
349 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
346 return true;
350 return true;
347 }
351 }
348 return false;
352 return false;
349 };
353 };
350
354
351 /**
355 /**
352 * enter the command mode for the cell
356 * enter the command mode for the cell
353 * @method command_mode
357 * @method command_mode
354 * @return is the action being taken
358 * @return is the action being taken
355 */
359 */
356 Cell.prototype.command_mode = function () {
360 Cell.prototype.command_mode = function () {
357 if (this.mode !== 'command') {
361 if (this.mode !== 'command') {
358 this.mode = 'command';
362 this.mode = 'command';
359 return true;
363 return true;
360 } else {
364 } else {
361 return false;
365 return false;
362 }
366 }
363 };
367 };
364
368
365 /**
369 /**
366 * enter the edit mode for the cell
370 * enter the edit mode for the cell
367 * @method command_mode
371 * @method command_mode
368 * @return is the action being taken
372 * @return is the action being taken
369 */
373 */
370 Cell.prototype.edit_mode = function () {
374 Cell.prototype.edit_mode = function () {
371 if (this.mode !== 'edit') {
375 if (this.mode !== 'edit') {
372 this.mode = 'edit';
376 this.mode = 'edit';
373 return true;
377 return true;
374 } else {
378 } else {
375 return false;
379 return false;
376 }
380 }
377 };
381 };
378
382
379 Cell.prototype.ensure_focused = function() {
383 Cell.prototype.ensure_focused = function() {
380 if(this.element !== document.activeElement && !this.code_mirror.hasFocus()){
384 if(this.element !== document.activeElement && !this.code_mirror.hasFocus()){
381 this.focus_cell();
385 this.focus_cell();
382 }
386 }
383 }
387 }
384
388
385 /**
389 /**
386 * Focus the cell in the DOM sense
390 * Focus the cell in the DOM sense
387 * @method focus_cell
391 * @method focus_cell
388 */
392 */
389 Cell.prototype.focus_cell = function () {
393 Cell.prototype.focus_cell = function () {
390 this.element.focus();
394 this.element.focus();
391 };
395 };
392
396
393 /**
397 /**
394 * Focus the editor area so a user can type
398 * Focus the editor area so a user can type
395 *
399 *
396 * NOTE: If codemirror is focused via a mouse click event, you don't want to
400 * NOTE: If codemirror is focused via a mouse click event, you don't want to
397 * call this because it will cause a page jump.
401 * call this because it will cause a page jump.
398 * @method focus_editor
402 * @method focus_editor
399 */
403 */
400 Cell.prototype.focus_editor = function () {
404 Cell.prototype.focus_editor = function () {
401 this.refresh();
405 this.refresh();
402 this.code_mirror.focus();
406 this.code_mirror.focus();
403 };
407 };
404
408
405 /**
409 /**
406 * Refresh codemirror instance
410 * Refresh codemirror instance
407 * @method refresh
411 * @method refresh
408 */
412 */
409 Cell.prototype.refresh = function () {
413 Cell.prototype.refresh = function () {
410 if (this.code_mirror) {
414 if (this.code_mirror) {
411 this.code_mirror.refresh();
415 this.code_mirror.refresh();
412 }
416 }
413 };
417 };
414
418
415 /**
419 /**
416 * should be overritten by subclass
420 * should be overritten by subclass
417 * @method get_text
421 * @method get_text
418 */
422 */
419 Cell.prototype.get_text = function () {
423 Cell.prototype.get_text = function () {
420 };
424 };
421
425
422 /**
426 /**
423 * should be overritten by subclass
427 * should be overritten by subclass
424 * @method set_text
428 * @method set_text
425 * @param {string} text
429 * @param {string} text
426 */
430 */
427 Cell.prototype.set_text = function (text) {
431 Cell.prototype.set_text = function (text) {
428 };
432 };
429
433
430 /**
434 /**
431 * should be overritten by subclass
435 * should be overritten by subclass
432 * serialise cell to json.
436 * serialise cell to json.
433 * @method toJSON
437 * @method toJSON
434 **/
438 **/
435 Cell.prototype.toJSON = function () {
439 Cell.prototype.toJSON = function () {
436 var data = {};
440 var data = {};
437 // deepcopy the metadata so copied cells don't share the same object
441 // deepcopy the metadata so copied cells don't share the same object
438 data.metadata = JSON.parse(JSON.stringify(this.metadata));
442 data.metadata = JSON.parse(JSON.stringify(this.metadata));
439 data.cell_type = this.cell_type;
443 data.cell_type = this.cell_type;
440 return data;
444 return data;
441 };
445 };
442
446
443 /**
447 /**
444 * should be overritten by subclass
448 * should be overritten by subclass
445 * @method fromJSON
449 * @method fromJSON
446 **/
450 **/
447 Cell.prototype.fromJSON = function (data) {
451 Cell.prototype.fromJSON = function (data) {
448 if (data.metadata !== undefined) {
452 if (data.metadata !== undefined) {
449 this.metadata = data.metadata;
453 this.metadata = data.metadata;
450 }
454 }
451 };
455 };
452
456
453
457
454 /**
458 /**
455 * can the cell be split into two cells (false if not deletable)
459 * can the cell be split into two cells (false if not deletable)
456 * @method is_splittable
460 * @method is_splittable
457 **/
461 **/
458 Cell.prototype.is_splittable = function () {
462 Cell.prototype.is_splittable = function () {
459 return this.is_deletable();
463 return this.is_deletable();
460 };
464 };
461
465
462
466
463 /**
467 /**
464 * can the cell be merged with other cells (false if not deletable)
468 * can the cell be merged with other cells (false if not deletable)
465 * @method is_mergeable
469 * @method is_mergeable
466 **/
470 **/
467 Cell.prototype.is_mergeable = function () {
471 Cell.prototype.is_mergeable = function () {
468 return this.is_deletable();
472 return this.is_deletable();
469 };
473 };
470
474
471 /**
475 /**
472 * is the cell deletable? only false (undeletable) if
476 * is the cell deletable? only false (undeletable) if
473 * metadata.deletable is explicitly false -- everything else
477 * metadata.deletable is explicitly false -- everything else
474 * counts as true
478 * counts as true
475 *
479 *
476 * @method is_deletable
480 * @method is_deletable
477 **/
481 **/
478 Cell.prototype.is_deletable = function () {
482 Cell.prototype.is_deletable = function () {
479 if (this.metadata.deletable === false) {
483 if (this.metadata.deletable === false) {
480 return false;
484 return false;
481 }
485 }
482 return true;
486 return true;
483 };
487 };
484
488
485 /**
489 /**
486 * @return {String} - the text before the cursor
490 * @return {String} - the text before the cursor
487 * @method get_pre_cursor
491 * @method get_pre_cursor
488 **/
492 **/
489 Cell.prototype.get_pre_cursor = function () {
493 Cell.prototype.get_pre_cursor = function () {
490 var cursor = this.code_mirror.getCursor();
494 var cursor = this.code_mirror.getCursor();
491 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
495 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
492 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
496 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
493 return text;
497 return text;
494 };
498 };
495
499
496
500
497 /**
501 /**
498 * @return {String} - the text after the cursor
502 * @return {String} - the text after the cursor
499 * @method get_post_cursor
503 * @method get_post_cursor
500 **/
504 **/
501 Cell.prototype.get_post_cursor = function () {
505 Cell.prototype.get_post_cursor = function () {
502 var cursor = this.code_mirror.getCursor();
506 var cursor = this.code_mirror.getCursor();
503 var last_line_num = this.code_mirror.lineCount()-1;
507 var last_line_num = this.code_mirror.lineCount()-1;
504 var last_line_len = this.code_mirror.getLine(last_line_num).length;
508 var last_line_len = this.code_mirror.getLine(last_line_num).length;
505 var end = {line:last_line_num, ch:last_line_len};
509 var end = {line:last_line_num, ch:last_line_len};
506 var text = this.code_mirror.getRange(cursor, end);
510 var text = this.code_mirror.getRange(cursor, end);
507 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
511 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
508 return text;
512 return text;
509 };
513 };
510
514
511 /**
515 /**
512 * Show/Hide CodeMirror LineNumber
516 * Show/Hide CodeMirror LineNumber
513 * @method show_line_numbers
517 * @method show_line_numbers
514 *
518 *
515 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
519 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
516 **/
520 **/
517 Cell.prototype.show_line_numbers = function (value) {
521 Cell.prototype.show_line_numbers = function (value) {
518 this.code_mirror.setOption('lineNumbers', value);
522 this.code_mirror.setOption('lineNumbers', value);
519 this.code_mirror.refresh();
523 this.code_mirror.refresh();
520 };
524 };
521
525
522 /**
526 /**
523 * Toggle CodeMirror LineNumber
527 * Toggle CodeMirror LineNumber
524 * @method toggle_line_numbers
528 * @method toggle_line_numbers
525 **/
529 **/
526 Cell.prototype.toggle_line_numbers = function () {
530 Cell.prototype.toggle_line_numbers = function () {
527 var val = this.code_mirror.getOption('lineNumbers');
531 var val = this.code_mirror.getOption('lineNumbers');
528 this.show_line_numbers(!val);
532 this.show_line_numbers(!val);
529 };
533 };
530
534
531 /**
535 /**
532 * Force codemirror highlight mode
536 * Force codemirror highlight mode
533 * @method force_highlight
537 * @method force_highlight
534 * @param {object} - CodeMirror mode
538 * @param {object} - CodeMirror mode
535 **/
539 **/
536 Cell.prototype.force_highlight = function(mode) {
540 Cell.prototype.force_highlight = function(mode) {
537 this.user_highlight = mode;
541 this.user_highlight = mode;
538 this.auto_highlight();
542 this.auto_highlight();
539 };
543 };
540
544
541 /**
545 /**
542 * Trigger autodetection of highlight scheme for current cell
546 * Trigger autodetection of highlight scheme for current cell
543 * @method auto_highlight
547 * @method auto_highlight
544 */
548 */
545 Cell.prototype.auto_highlight = function () {
549 Cell.prototype.auto_highlight = function () {
546 this._auto_highlight(this.class_config.get_sync('highlight_modes'));
550 this._auto_highlight(this.class_config.get_sync('highlight_modes'));
547 };
551 };
548
552
549 /**
553 /**
550 * Try to autodetect cell highlight mode, or use selected mode
554 * Try to autodetect cell highlight mode, or use selected mode
551 * @methods _auto_highlight
555 * @methods _auto_highlight
552 * @private
556 * @private
553 * @param {String|object|undefined} - CodeMirror mode | 'auto'
557 * @param {String|object|undefined} - CodeMirror mode | 'auto'
554 **/
558 **/
555 Cell.prototype._auto_highlight = function (modes) {
559 Cell.prototype._auto_highlight = function (modes) {
556 /**
560 /**
557 *Here we handle manually selected modes
561 *Here we handle manually selected modes
558 */
562 */
559 var that = this;
563 var that = this;
560 var mode;
564 var mode;
561 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
565 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
562 {
566 {
563 mode = this.user_highlight;
567 mode = this.user_highlight;
564 CodeMirror.autoLoadMode(this.code_mirror, mode);
568 CodeMirror.autoLoadMode(this.code_mirror, mode);
565 this.code_mirror.setOption('mode', mode);
569 this.code_mirror.setOption('mode', mode);
566 return;
570 return;
567 }
571 }
568 var current_mode = this.code_mirror.getOption('mode', mode);
572 var current_mode = this.code_mirror.getOption('mode', mode);
569 var first_line = this.code_mirror.getLine(0);
573 var first_line = this.code_mirror.getLine(0);
570 // loop on every pairs
574 // loop on every pairs
571 for(mode in modes) {
575 for(mode in modes) {
572 var regs = modes[mode].reg;
576 var regs = modes[mode].reg;
573 // only one key every time but regexp can't be keys...
577 // only one key every time but regexp can't be keys...
574 for(var i=0; i<regs.length; i++) {
578 for(var i=0; i<regs.length; i++) {
575 // here we handle non magic_modes.
579 // here we handle non magic_modes.
576 // TODO :
580 // TODO :
577 // On 3.0 and below, these things were regex.
581 // On 3.0 and below, these things were regex.
578 // But now should be string for json-able config.
582 // But now should be string for json-able config.
579 // We should get rid of assuming they might be already
583 // We should get rid of assuming they might be already
580 // in a later version of IPython.
584 // in a later version of IPython.
581 var re = regs[i];
585 var re = regs[i];
582 if(typeof(re) === 'string'){
586 if(typeof(re) === 'string'){
583 re = new RegExp(re)
587 re = new RegExp(re)
584 }
588 }
585 if(first_line.match(re) !== null) {
589 if(first_line.match(re) !== null) {
586 if(current_mode == mode){
590 if(current_mode == mode){
587 return;
591 return;
588 }
592 }
589 if (mode.search('magic_') !== 0) {
593 if (mode.search('magic_') !== 0) {
590 utils.requireCodeMirrorMode(mode, function (spec) {
594 utils.requireCodeMirrorMode(mode, function (spec) {
591 that.code_mirror.setOption('mode', spec);
595 that.code_mirror.setOption('mode', spec);
592 });
596 });
593 return;
597 return;
594 }
598 }
595 var open = modes[mode].open || "%%";
599 var open = modes[mode].open || "%%";
596 var close = modes[mode].close || "%%end";
600 var close = modes[mode].close || "%%end";
597 var magic_mode = mode;
601 var magic_mode = mode;
598 mode = magic_mode.substr(6);
602 mode = magic_mode.substr(6);
599 if(current_mode == magic_mode){
603 if(current_mode == magic_mode){
600 return;
604 return;
601 }
605 }
602 utils.requireCodeMirrorMode(mode, function (spec) {
606 utils.requireCodeMirrorMode(mode, function (spec) {
603 // create on the fly a mode that switch between
607 // create on the fly a mode that switch between
604 // plain/text and something else, otherwise `%%` is
608 // plain/text and something else, otherwise `%%` is
605 // source of some highlight issues.
609 // source of some highlight issues.
606 CodeMirror.defineMode(magic_mode, function(config) {
610 CodeMirror.defineMode(magic_mode, function(config) {
607 return CodeMirror.multiplexingMode(
611 return CodeMirror.multiplexingMode(
608 CodeMirror.getMode(config, 'text/plain'),
612 CodeMirror.getMode(config, 'text/plain'),
609 // always set something on close
613 // always set something on close
610 {open: open, close: close,
614 {open: open, close: close,
611 mode: CodeMirror.getMode(config, spec),
615 mode: CodeMirror.getMode(config, spec),
612 delimStyle: "delimit"
616 delimStyle: "delimit"
613 }
617 }
614 );
618 );
615 });
619 });
616 that.code_mirror.setOption('mode', magic_mode);
620 that.code_mirror.setOption('mode', magic_mode);
617 });
621 });
618 return;
622 return;
619 }
623 }
620 }
624 }
621 }
625 }
622 // fallback on default
626 // fallback on default
623 var default_mode;
627 var default_mode;
624 try {
628 try {
625 default_mode = this._options.cm_config.mode;
629 default_mode = this._options.cm_config.mode;
626 } catch(e) {
630 } catch(e) {
627 default_mode = 'text/plain';
631 default_mode = 'text/plain';
628 }
632 }
629 if( current_mode === default_mode){
633 if( current_mode === default_mode){
630 return;
634 return;
631 }
635 }
632 this.code_mirror.setOption('mode', default_mode);
636 this.code_mirror.setOption('mode', default_mode);
633 };
637 };
634
638
635 var UnrecognizedCell = function (options) {
639 var UnrecognizedCell = function (options) {
636 /** Constructor for unrecognized cells */
640 /** Constructor for unrecognized cells */
637 Cell.apply(this, arguments);
641 Cell.apply(this, arguments);
638 this.cell_type = 'unrecognized';
642 this.cell_type = 'unrecognized';
639 this.celltoolbar = null;
643 this.celltoolbar = null;
640 this.data = {};
644 this.data = {};
641
645
642 Object.seal(this);
646 Object.seal(this);
643 };
647 };
644
648
645 UnrecognizedCell.prototype = Object.create(Cell.prototype);
649 UnrecognizedCell.prototype = Object.create(Cell.prototype);
646
650
647
651
648 // cannot merge or split unrecognized cells
652 // cannot merge or split unrecognized cells
649 UnrecognizedCell.prototype.is_mergeable = function () {
653 UnrecognizedCell.prototype.is_mergeable = function () {
650 return false;
654 return false;
651 };
655 };
652
656
653 UnrecognizedCell.prototype.is_splittable = function () {
657 UnrecognizedCell.prototype.is_splittable = function () {
654 return false;
658 return false;
655 };
659 };
656
660
657 UnrecognizedCell.prototype.toJSON = function () {
661 UnrecognizedCell.prototype.toJSON = function () {
658 /**
662 /**
659 * deepcopy the metadata so copied cells don't share the same object
663 * deepcopy the metadata so copied cells don't share the same object
660 */
664 */
661 return JSON.parse(JSON.stringify(this.data));
665 return JSON.parse(JSON.stringify(this.data));
662 };
666 };
663
667
664 UnrecognizedCell.prototype.fromJSON = function (data) {
668 UnrecognizedCell.prototype.fromJSON = function (data) {
665 this.data = data;
669 this.data = data;
666 if (data.metadata !== undefined) {
670 if (data.metadata !== undefined) {
667 this.metadata = data.metadata;
671 this.metadata = data.metadata;
668 } else {
672 } else {
669 data.metadata = this.metadata;
673 data.metadata = this.metadata;
670 }
674 }
671 this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type);
675 this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type);
672 };
676 };
673
677
674 UnrecognizedCell.prototype.create_element = function () {
678 UnrecognizedCell.prototype.create_element = function () {
675 Cell.prototype.create_element.apply(this, arguments);
679 Cell.prototype.create_element.apply(this, arguments);
676 var cell = this.element = $("<div>").addClass('cell unrecognized_cell');
680 var cell = this.element = $("<div>").addClass('cell unrecognized_cell');
677 cell.attr('tabindex','2');
681 cell.attr('tabindex','2');
678
682
679 var prompt = $('<div/>').addClass('prompt input_prompt');
683 var prompt = $('<div/>').addClass('prompt input_prompt');
680 cell.append(prompt);
684 cell.append(prompt);
681 var inner_cell = $('<div/>').addClass('inner_cell');
685 var inner_cell = $('<div/>').addClass('inner_cell');
682 inner_cell.append(
686 inner_cell.append(
683 $("<a>")
687 $("<a>")
684 .attr("href", "#")
688 .attr("href", "#")
685 .text("Unrecognized cell type")
689 .text("Unrecognized cell type")
686 );
690 );
687 cell.append(inner_cell);
691 cell.append(inner_cell);
688 this.element = cell;
692 this.element = cell;
689 };
693 };
690
694
691 UnrecognizedCell.prototype.bind_events = function () {
695 UnrecognizedCell.prototype.bind_events = function () {
692 Cell.prototype.bind_events.apply(this, arguments);
696 Cell.prototype.bind_events.apply(this, arguments);
693 var cell = this;
697 var cell = this;
694
698
695 this.element.find('.inner_cell').find("a").click(function () {
699 this.element.find('.inner_cell').find("a").click(function () {
696 cell.events.trigger('unrecognized_cell.Cell', {cell: cell});
700 cell.events.trigger('unrecognized_cell.Cell', {cell: cell});
697 });
701 });
698 };
702 };
699
703
700 // Backwards compatibility.
704 // Backwards compatibility.
701 IPython.Cell = Cell;
705 IPython.Cell = Cell;
702
706
703 return {
707 return {
704 Cell: Cell,
708 Cell: Cell,
705 UnrecognizedCell: UnrecognizedCell
709 UnrecognizedCell: UnrecognizedCell
706 };
710 };
707 });
711 });
@@ -1,664 +1,664 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3 /**
3 /**
4 *
4 *
5 *
5 *
6 * @module codecell
6 * @module codecell
7 * @namespace codecell
7 * @namespace codecell
8 * @class CodeCell
8 * @class CodeCell
9 */
9 */
10
10
11
11
12 define([
12 define([
13 'base/js/namespace',
13 'base/js/namespace',
14 'jquery',
14 'jquery',
15 'base/js/utils',
15 'base/js/utils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'services/config',
17 'services/config',
18 'notebook/js/cell',
18 'notebook/js/cell',
19 'notebook/js/outputarea',
19 'notebook/js/outputarea',
20 'notebook/js/completer',
20 'notebook/js/completer',
21 'notebook/js/celltoolbar',
21 'notebook/js/celltoolbar',
22 'codemirror/lib/codemirror',
22 'codemirror/lib/codemirror',
23 'codemirror/mode/python/python',
23 'codemirror/mode/python/python',
24 'notebook/js/codemirror-ipython'
24 'notebook/js/codemirror-ipython'
25 ], function(IPython,
25 ], function(IPython,
26 $,
26 $,
27 utils,
27 utils,
28 keyboard,
28 keyboard,
29 configmod,
29 configmod,
30 cell,
30 cell,
31 outputarea,
31 outputarea,
32 completer,
32 completer,
33 celltoolbar,
33 celltoolbar,
34 CodeMirror,
34 CodeMirror,
35 cmpython,
35 cmpython,
36 cmip
36 cmip
37 ) {
37 ) {
38 "use strict";
38 "use strict";
39
39
40 var Cell = cell.Cell;
40 var Cell = cell.Cell;
41
41
42 /* local util for codemirror */
42 /* local util for codemirror */
43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
44
44
45 /**
45 /**
46 *
46 *
47 * function to delete until previous non blanking space character
47 * function to delete until previous non blanking space character
48 * or first multiple of 4 tabstop.
48 * or first multiple of 4 tabstop.
49 * @private
49 * @private
50 */
50 */
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
55 var tabsize = cm.getOption('tabSize');
55 var tabsize = cm.getOption('tabSize');
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
58 var select = cm.getRange(from,cur);
58 var select = cm.getRange(from,cur);
59 if( select.match(/^\ +$/) !== null){
59 if( select.match(/^\ +$/) !== null){
60 cm.replaceRange("",from,cur);
60 cm.replaceRange("",from,cur);
61 } else {
61 } else {
62 cm.deleteH(-1,"char");
62 cm.deleteH(-1,"char");
63 }
63 }
64 };
64 };
65
65
66 var keycodes = keyboard.keycodes;
66 var keycodes = keyboard.keycodes;
67
67
68 var CodeCell = function (kernel, options) {
68 var CodeCell = function (kernel, options) {
69 /**
69 /**
70 * Constructor
70 * Constructor
71 *
71 *
72 * A Cell conceived to write code.
72 * A Cell conceived to write code.
73 *
73 *
74 * Parameters:
74 * Parameters:
75 * kernel: Kernel instance
75 * kernel: Kernel instance
76 * The kernel doesn't have to be set at creation time, in that case
76 * The kernel doesn't have to be set at creation time, in that case
77 * it will be null and set_kernel has to be called later.
77 * it will be null and set_kernel has to be called later.
78 * options: dictionary
78 * options: dictionary
79 * Dictionary of keyword arguments.
79 * Dictionary of keyword arguments.
80 * events: $(Events) instance
80 * events: $(Events) instance
81 * config: dictionary
81 * config: dictionary
82 * keyboard_manager: KeyboardManager instance
82 * keyboard_manager: KeyboardManager instance
83 * notebook: Notebook instance
83 * notebook: Notebook instance
84 * tooltip: Tooltip instance
84 * tooltip: Tooltip instance
85 */
85 */
86 this.kernel = kernel || null;
86 this.kernel = kernel || null;
87 this.notebook = options.notebook;
87 this.notebook = options.notebook;
88 this.collapsed = false;
88 this.collapsed = false;
89 this.events = options.events;
89 this.events = options.events;
90 this.tooltip = options.tooltip;
90 this.tooltip = options.tooltip;
91 this.config = options.config;
91 this.config = options.config;
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
93 CodeCell.config_defaults, 'CodeCell');
93 CodeCell.config_defaults, 'CodeCell');
94
94
95 // create all attributed in constructor function
95 // create all attributed in constructor function
96 // even if null for V8 VM optimisation
96 // even if null for V8 VM optimisation
97 this.input_prompt_number = null;
97 this.input_prompt_number = null;
98 this.celltoolbar = null;
98 this.celltoolbar = null;
99 this.output_area = null;
99 this.output_area = null;
100
100
101 this.last_msg_id = null;
101 this.last_msg_id = null;
102 this.completer = null;
102 this.completer = null;
103 this.widget_views = [];
103 this.widget_views = [];
104 this._widgets_live = true;
104 this._widgets_live = true;
105
105
106 Cell.apply(this,[{
106 Cell.apply(this,[{
107 config: $.extend({}, CodeCell.options_default),
107 config: $.extend({}, CodeCell.options_default),
108 keyboard_manager: options.keyboard_manager,
108 keyboard_manager: options.keyboard_manager,
109 events: this.events}]);
109 events: this.events}]);
110
110
111 // Attributes we want to override in this subclass.
111 // Attributes we want to override in this subclass.
112 this.cell_type = "code";
112 this.cell_type = "code";
113 var that = this;
113 var that = this;
114 this.element.focusout(
114 this.element.focusout(
115 function() { that.auto_highlight(); }
115 function() { that.auto_highlight(); }
116 );
116 );
117 };
117 };
118
118
119 CodeCell.options_default = {
119 CodeCell.options_default = {
120 cm_config : {
120 cm_config : {
121 extraKeys: {
121 extraKeys: {
122 "Tab" : "indentMore",
122 "Tab" : "indentMore",
123 "Shift-Tab" : "indentLess",
123 "Shift-Tab" : "indentLess",
124 "Backspace" : "delSpaceToPrevTabStop",
124 "Backspace" : "delSpaceToPrevTabStop",
125 "Cmd-/" : "toggleComment",
125 "Cmd-/" : "toggleComment",
126 "Ctrl-/" : "toggleComment"
126 "Ctrl-/" : "toggleComment"
127 },
127 },
128 mode: 'ipython',
128 mode: 'ipython',
129 theme: 'ipython',
129 theme: 'ipython',
130 matchBrackets: true,
130 matchBrackets: true,
131 autoCloseBrackets: true
131 autoCloseBrackets: true
132 },
132 },
133 highlight_modes : {
133 highlight_modes : {
134 'magic_javascript' :{'reg':['^%%javascript']},
134 'magic_javascript' :{'reg':['^%%javascript']},
135 'magic_perl' :{'reg':['^%%perl']},
135 'magic_perl' :{'reg':['^%%perl']},
136 'magic_ruby' :{'reg':['^%%ruby']},
136 'magic_ruby' :{'reg':['^%%ruby']},
137 'magic_python' :{'reg':['^%%python3?']},
137 'magic_python' :{'reg':['^%%python3?']},
138 'magic_shell' :{'reg':['^%%bash']},
138 'magic_shell' :{'reg':['^%%bash']},
139 'magic_r' :{'reg':['^%%R']},
139 'magic_r' :{'reg':['^%%R']},
140 'magic_text/x-cython' :{'reg':['^%%cython']},
140 'magic_text/x-cython' :{'reg':['^%%cython']},
141 },
141 },
142 };
142 };
143
143
144 CodeCell.config_defaults = CodeCell.options_default;
144 CodeCell.config_defaults = CodeCell.options_default;
145
145
146 CodeCell.msg_cells = {};
146 CodeCell.msg_cells = {};
147
147
148 CodeCell.prototype = Object.create(Cell.prototype);
148 CodeCell.prototype = Object.create(Cell.prototype);
149
149
150 /** @method create_element */
150 /** @method create_element */
151 CodeCell.prototype.create_element = function () {
151 CodeCell.prototype.create_element = function () {
152 Cell.prototype.create_element.apply(this, arguments);
152 Cell.prototype.create_element.apply(this, arguments);
153 var that = this;
153 var that = this;
154
154
155 var cell = $('<div></div>').addClass('cell code_cell');
155 var cell = $('<div></div>').addClass('cell code_cell');
156 cell.attr('tabindex','2');
156 cell.attr('tabindex','2');
157
157
158 var input = $('<div></div>').addClass('input');
158 var input = $('<div></div>').addClass('input');
159 var prompt = $('<div/>').addClass('prompt input_prompt');
159 var prompt = $('<div/>').addClass('prompt input_prompt');
160 var inner_cell = $('<div/>').addClass('inner_cell');
160 var inner_cell = $('<div/>').addClass('inner_cell');
161 this.celltoolbar = new celltoolbar.CellToolbar({
161 this.celltoolbar = new celltoolbar.CellToolbar({
162 cell: this,
162 cell: this,
163 notebook: this.notebook});
163 notebook: this.notebook});
164 inner_cell.append(this.celltoolbar.element);
164 inner_cell.append(this.celltoolbar.element);
165 var input_area = $('<div/>').addClass('input_area');
165 var input_area = $('<div/>').addClass('input_area');
166 this.code_mirror = new CodeMirror(input_area.get(0), this.class_config.get_sync('cm_config'));
166 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
167 // In case of bugs that put the keyboard manager into an inconsistent state,
167 // In case of bugs that put the keyboard manager into an inconsistent state,
168 // ensure KM is enabled when CodeMirror is focused:
168 // ensure KM is enabled when CodeMirror is focused:
169 this.code_mirror.on('focus', function () {
169 this.code_mirror.on('focus', function () {
170 if (that.keyboard_manager) {
170 if (that.keyboard_manager) {
171 that.keyboard_manager.enable();
171 that.keyboard_manager.enable();
172 }
172 }
173 });
173 });
174 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
174 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
175 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
175 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
176 inner_cell.append(input_area);
176 inner_cell.append(input_area);
177 input.append(prompt).append(inner_cell);
177 input.append(prompt).append(inner_cell);
178
178
179 var widget_area = $('<div/>')
179 var widget_area = $('<div/>')
180 .addClass('widget-area')
180 .addClass('widget-area')
181 .hide();
181 .hide();
182 this.widget_area = widget_area;
182 this.widget_area = widget_area;
183 var widget_prompt = $('<div/>')
183 var widget_prompt = $('<div/>')
184 .addClass('prompt')
184 .addClass('prompt')
185 .appendTo(widget_area);
185 .appendTo(widget_area);
186 var widget_subarea = $('<div/>')
186 var widget_subarea = $('<div/>')
187 .addClass('widget-subarea')
187 .addClass('widget-subarea')
188 .appendTo(widget_area);
188 .appendTo(widget_area);
189 this.widget_subarea = widget_subarea;
189 this.widget_subarea = widget_subarea;
190 var that = this;
190 var that = this;
191 var widget_clear_buton = $('<button />')
191 var widget_clear_buton = $('<button />')
192 .addClass('close')
192 .addClass('close')
193 .html('&times;')
193 .html('&times;')
194 .click(function() {
194 .click(function() {
195 widget_area.slideUp('', function(){
195 widget_area.slideUp('', function(){
196 for (var i = 0; i < that.widget_views.length; i++) {
196 for (var i = 0; i < that.widget_views.length; i++) {
197 var view = that.widget_views[i];
197 var view = that.widget_views[i];
198 view.remove();
198 view.remove();
199
199
200 // Remove widget live events.
200 // Remove widget live events.
201 view.off('comm:live', that._widget_live);
201 view.off('comm:live', that._widget_live);
202 view.off('comm:dead', that._widget_dead);
202 view.off('comm:dead', that._widget_dead);
203 }
203 }
204 that.widget_views = [];
204 that.widget_views = [];
205 widget_subarea.html('');
205 widget_subarea.html('');
206 });
206 });
207 })
207 })
208 .appendTo(widget_prompt);
208 .appendTo(widget_prompt);
209
209
210 var output = $('<div></div>');
210 var output = $('<div></div>');
211 cell.append(input).append(widget_area).append(output);
211 cell.append(input).append(widget_area).append(output);
212 this.element = cell;
212 this.element = cell;
213 this.output_area = new outputarea.OutputArea({
213 this.output_area = new outputarea.OutputArea({
214 selector: output,
214 selector: output,
215 prompt_area: true,
215 prompt_area: true,
216 events: this.events,
216 events: this.events,
217 keyboard_manager: this.keyboard_manager});
217 keyboard_manager: this.keyboard_manager});
218 this.completer = new completer.Completer(this, this.events);
218 this.completer = new completer.Completer(this, this.events);
219 };
219 };
220
220
221 /**
221 /**
222 * Display a widget view in the cell.
222 * Display a widget view in the cell.
223 */
223 */
224 CodeCell.prototype.display_widget_view = function(view_promise) {
224 CodeCell.prototype.display_widget_view = function(view_promise) {
225
225
226 // Display a dummy element
226 // Display a dummy element
227 var dummy = $('<div/>');
227 var dummy = $('<div/>');
228 this.widget_subarea.append(dummy);
228 this.widget_subarea.append(dummy);
229
229
230 // Display the view.
230 // Display the view.
231 var that = this;
231 var that = this;
232 return view_promise.then(function(view) {
232 return view_promise.then(function(view) {
233 that.widget_area.show();
233 that.widget_area.show();
234 dummy.replaceWith(view.$el);
234 dummy.replaceWith(view.$el);
235 that.widget_views.push(view);
235 that.widget_views.push(view);
236
236
237 // Check the live state of the view's model.
237 // Check the live state of the view's model.
238 if (view.model.comm_live) {
238 if (view.model.comm_live) {
239 that._widget_live(view);
239 that._widget_live(view);
240 } else {
240 } else {
241 that._widget_dead(view);
241 that._widget_dead(view);
242 }
242 }
243
243
244 // Listen to comm live events for the view.
244 // Listen to comm live events for the view.
245 view.on('comm:live', that._widget_live, that);
245 view.on('comm:live', that._widget_live, that);
246 view.on('comm:dead', that._widget_dead, that);
246 view.on('comm:dead', that._widget_dead, that);
247 return view;
247 return view;
248 });
248 });
249 };
249 };
250
250
251 /**
251 /**
252 * Handles when a widget loses it's comm connection.
252 * Handles when a widget loses it's comm connection.
253 * @param {WidgetView} view
253 * @param {WidgetView} view
254 */
254 */
255 CodeCell.prototype._widget_dead = function(view) {
255 CodeCell.prototype._widget_dead = function(view) {
256 if (this._widgets_live) {
256 if (this._widgets_live) {
257 this._widgets_live = false;
257 this._widgets_live = false;
258 this.widget_area.addClass('connection-problems');
258 this.widget_area.addClass('connection-problems');
259 }
259 }
260
260
261 };
261 };
262
262
263 /**
263 /**
264 * Handles when a widget is connected to a live comm.
264 * Handles when a widget is connected to a live comm.
265 * @param {WidgetView} view
265 * @param {WidgetView} view
266 */
266 */
267 CodeCell.prototype._widget_live = function(view) {
267 CodeCell.prototype._widget_live = function(view) {
268 if (!this._widgets_live) {
268 if (!this._widgets_live) {
269 // Check that the other widgets are live too. O(N) operation.
269 // Check that the other widgets are live too. O(N) operation.
270 // Abort the function at the first dead widget found.
270 // Abort the function at the first dead widget found.
271 for (var i = 0; i < this.widget_views.length; i++) {
271 for (var i = 0; i < this.widget_views.length; i++) {
272 if (!this.widget_views[i].model.comm_live) return;
272 if (!this.widget_views[i].model.comm_live) return;
273 }
273 }
274 this._widgets_live = true;
274 this._widgets_live = true;
275 this.widget_area.removeClass('connection-problems');
275 this.widget_area.removeClass('connection-problems');
276 }
276 }
277 };
277 };
278
278
279 /** @method bind_events */
279 /** @method bind_events */
280 CodeCell.prototype.bind_events = function () {
280 CodeCell.prototype.bind_events = function () {
281 Cell.prototype.bind_events.apply(this);
281 Cell.prototype.bind_events.apply(this);
282 var that = this;
282 var that = this;
283
283
284 this.element.focusout(
284 this.element.focusout(
285 function() { that.auto_highlight(); }
285 function() { that.auto_highlight(); }
286 );
286 );
287 };
287 };
288
288
289
289
290 /**
290 /**
291 * This method gets called in CodeMirror's onKeyDown/onKeyPress
291 * This method gets called in CodeMirror's onKeyDown/onKeyPress
292 * handlers and is used to provide custom key handling. Its return
292 * handlers and is used to provide custom key handling. Its return
293 * value is used to determine if CodeMirror should ignore the event:
293 * value is used to determine if CodeMirror should ignore the event:
294 * true = ignore, false = don't ignore.
294 * true = ignore, false = don't ignore.
295 * @method handle_codemirror_keyevent
295 * @method handle_codemirror_keyevent
296 */
296 */
297
297
298 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
298 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
299
299
300 var that = this;
300 var that = this;
301 // whatever key is pressed, first, cancel the tooltip request before
301 // whatever key is pressed, first, cancel the tooltip request before
302 // they are sent, and remove tooltip if any, except for tab again
302 // they are sent, and remove tooltip if any, except for tab again
303 var tooltip_closed = null;
303 var tooltip_closed = null;
304 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
304 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
305 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
305 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
306 }
306 }
307
307
308 var cur = editor.getCursor();
308 var cur = editor.getCursor();
309 if (event.keyCode === keycodes.enter){
309 if (event.keyCode === keycodes.enter){
310 this.auto_highlight();
310 this.auto_highlight();
311 }
311 }
312
312
313 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
313 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
314 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
314 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
315 // browser and keyboard layout !
315 // browser and keyboard layout !
316 // Pressing '(' , request tooltip, don't forget to reappend it
316 // Pressing '(' , request tooltip, don't forget to reappend it
317 // The second argument says to hide the tooltip if the docstring
317 // The second argument says to hide the tooltip if the docstring
318 // is actually empty
318 // is actually empty
319 this.tooltip.pending(that, true);
319 this.tooltip.pending(that, true);
320 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
320 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
321 // If tooltip is active, cancel it. The call to
321 // If tooltip is active, cancel it. The call to
322 // remove_and_cancel_tooltip above doesn't pass, force=true.
322 // remove_and_cancel_tooltip above doesn't pass, force=true.
323 // Because of this it won't actually close the tooltip
323 // Because of this it won't actually close the tooltip
324 // if it is in sticky mode. Thus, we have to check again if it is open
324 // if it is in sticky mode. Thus, we have to check again if it is open
325 // and close it with force=true.
325 // and close it with force=true.
326 if (!this.tooltip._hidden) {
326 if (!this.tooltip._hidden) {
327 this.tooltip.remove_and_cancel_tooltip(true);
327 this.tooltip.remove_and_cancel_tooltip(true);
328 }
328 }
329 // If we closed the tooltip, don't let CM or the global handlers
329 // If we closed the tooltip, don't let CM or the global handlers
330 // handle this event.
330 // handle this event.
331 event.codemirrorIgnore = true;
331 event.codemirrorIgnore = true;
332 event._ipkmIgnore = true;
332 event._ipkmIgnore = true;
333 event.preventDefault();
333 event.preventDefault();
334 return true;
334 return true;
335 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
335 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
336 if (editor.somethingSelected() || editor.getSelections().length !== 1){
336 if (editor.somethingSelected() || editor.getSelections().length !== 1){
337 var anchor = editor.getCursor("anchor");
337 var anchor = editor.getCursor("anchor");
338 var head = editor.getCursor("head");
338 var head = editor.getCursor("head");
339 if( anchor.line !== head.line){
339 if( anchor.line !== head.line){
340 return false;
340 return false;
341 }
341 }
342 }
342 }
343 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
343 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
344 if (pre_cursor.trim() === "") {
344 if (pre_cursor.trim() === "") {
345 // Don't show tooltip if the part of the line before the cursor
345 // Don't show tooltip if the part of the line before the cursor
346 // is empty. In this case, let CodeMirror handle indentation.
346 // is empty. In this case, let CodeMirror handle indentation.
347 return false;
347 return false;
348 }
348 }
349 this.tooltip.request(that);
349 this.tooltip.request(that);
350 event.codemirrorIgnore = true;
350 event.codemirrorIgnore = true;
351 event.preventDefault();
351 event.preventDefault();
352 return true;
352 return true;
353 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
353 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
354 // Tab completion.
354 // Tab completion.
355 this.tooltip.remove_and_cancel_tooltip();
355 this.tooltip.remove_and_cancel_tooltip();
356
356
357 // completion does not work on multicursor, it might be possible though in some cases
357 // completion does not work on multicursor, it might be possible though in some cases
358 if (editor.somethingSelected() || editor.getSelections().length > 1) {
358 if (editor.somethingSelected() || editor.getSelections().length > 1) {
359 return false;
359 return false;
360 }
360 }
361 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
361 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
362 if (pre_cursor.trim() === "") {
362 if (pre_cursor.trim() === "") {
363 // Don't autocomplete if the part of the line before the cursor
363 // Don't autocomplete if the part of the line before the cursor
364 // is empty. In this case, let CodeMirror handle indentation.
364 // is empty. In this case, let CodeMirror handle indentation.
365 return false;
365 return false;
366 } else {
366 } else {
367 event.codemirrorIgnore = true;
367 event.codemirrorIgnore = true;
368 event.preventDefault();
368 event.preventDefault();
369 this.completer.startCompletion();
369 this.completer.startCompletion();
370 return true;
370 return true;
371 }
371 }
372 }
372 }
373
373
374 // keyboard event wasn't one of those unique to code cells, let's see
374 // keyboard event wasn't one of those unique to code cells, let's see
375 // if it's one of the generic ones (i.e. check edit mode shortcuts)
375 // if it's one of the generic ones (i.e. check edit mode shortcuts)
376 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
376 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
377 };
377 };
378
378
379 // Kernel related calls.
379 // Kernel related calls.
380
380
381 CodeCell.prototype.set_kernel = function (kernel) {
381 CodeCell.prototype.set_kernel = function (kernel) {
382 this.kernel = kernel;
382 this.kernel = kernel;
383 };
383 };
384
384
385 /**
385 /**
386 * Execute current code cell to the kernel
386 * Execute current code cell to the kernel
387 * @method execute
387 * @method execute
388 */
388 */
389 CodeCell.prototype.execute = function (stop_on_error) {
389 CodeCell.prototype.execute = function (stop_on_error) {
390 if (!this.kernel || !this.kernel.is_connected()) {
390 if (!this.kernel || !this.kernel.is_connected()) {
391 console.log("Can't execute, kernel is not connected.");
391 console.log("Can't execute, kernel is not connected.");
392 return;
392 return;
393 }
393 }
394
394
395 this.output_area.clear_output(false, true);
395 this.output_area.clear_output(false, true);
396
396
397 if (stop_on_error === undefined) {
397 if (stop_on_error === undefined) {
398 stop_on_error = true;
398 stop_on_error = true;
399 }
399 }
400
400
401 // Clear widget area
401 // Clear widget area
402 for (var i = 0; i < this.widget_views.length; i++) {
402 for (var i = 0; i < this.widget_views.length; i++) {
403 var view = this.widget_views[i];
403 var view = this.widget_views[i];
404 view.remove();
404 view.remove();
405
405
406 // Remove widget live events.
406 // Remove widget live events.
407 view.off('comm:live', this._widget_live);
407 view.off('comm:live', this._widget_live);
408 view.off('comm:dead', this._widget_dead);
408 view.off('comm:dead', this._widget_dead);
409 }
409 }
410 this.widget_views = [];
410 this.widget_views = [];
411 this.widget_subarea.html('');
411 this.widget_subarea.html('');
412 this.widget_subarea.height('');
412 this.widget_subarea.height('');
413 this.widget_area.height('');
413 this.widget_area.height('');
414 this.widget_area.hide();
414 this.widget_area.hide();
415
415
416 var old_msg_id = this.last_msg_id;
416 var old_msg_id = this.last_msg_id;
417
417
418 if (old_msg_id) {
418 if (old_msg_id) {
419 this.kernel.clear_callbacks_for_msg(old_msg_id);
419 this.kernel.clear_callbacks_for_msg(old_msg_id);
420 if (old_msg_id) {
420 if (old_msg_id) {
421 delete CodeCell.msg_cells[old_msg_id];
421 delete CodeCell.msg_cells[old_msg_id];
422 }
422 }
423 }
423 }
424 if (this.get_text().trim().length === 0) {
424 if (this.get_text().trim().length === 0) {
425 // nothing to do
425 // nothing to do
426 this.set_input_prompt(null);
426 this.set_input_prompt(null);
427 return;
427 return;
428 }
428 }
429 this.set_input_prompt('*');
429 this.set_input_prompt('*');
430 this.element.addClass("running");
430 this.element.addClass("running");
431 var callbacks = this.get_callbacks();
431 var callbacks = this.get_callbacks();
432
432
433 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
433 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
434 stop_on_error : stop_on_error});
434 stop_on_error : stop_on_error});
435 CodeCell.msg_cells[this.last_msg_id] = this;
435 CodeCell.msg_cells[this.last_msg_id] = this;
436 this.render();
436 this.render();
437 this.events.trigger('execute.CodeCell', {cell: this});
437 this.events.trigger('execute.CodeCell', {cell: this});
438 };
438 };
439
439
440 /**
440 /**
441 * Construct the default callbacks for
441 * Construct the default callbacks for
442 * @method get_callbacks
442 * @method get_callbacks
443 */
443 */
444 CodeCell.prototype.get_callbacks = function () {
444 CodeCell.prototype.get_callbacks = function () {
445 var that = this;
445 var that = this;
446 return {
446 return {
447 shell : {
447 shell : {
448 reply : $.proxy(this._handle_execute_reply, this),
448 reply : $.proxy(this._handle_execute_reply, this),
449 payload : {
449 payload : {
450 set_next_input : $.proxy(this._handle_set_next_input, this),
450 set_next_input : $.proxy(this._handle_set_next_input, this),
451 page : $.proxy(this._open_with_pager, this)
451 page : $.proxy(this._open_with_pager, this)
452 }
452 }
453 },
453 },
454 iopub : {
454 iopub : {
455 output : function() {
455 output : function() {
456 that.output_area.handle_output.apply(that.output_area, arguments);
456 that.output_area.handle_output.apply(that.output_area, arguments);
457 },
457 },
458 clear_output : function() {
458 clear_output : function() {
459 that.output_area.handle_clear_output.apply(that.output_area, arguments);
459 that.output_area.handle_clear_output.apply(that.output_area, arguments);
460 },
460 },
461 },
461 },
462 input : $.proxy(this._handle_input_request, this)
462 input : $.proxy(this._handle_input_request, this)
463 };
463 };
464 };
464 };
465
465
466 CodeCell.prototype._open_with_pager = function (payload) {
466 CodeCell.prototype._open_with_pager = function (payload) {
467 this.events.trigger('open_with_text.Pager', payload);
467 this.events.trigger('open_with_text.Pager', payload);
468 };
468 };
469
469
470 /**
470 /**
471 * @method _handle_execute_reply
471 * @method _handle_execute_reply
472 * @private
472 * @private
473 */
473 */
474 CodeCell.prototype._handle_execute_reply = function (msg) {
474 CodeCell.prototype._handle_execute_reply = function (msg) {
475 this.set_input_prompt(msg.content.execution_count);
475 this.set_input_prompt(msg.content.execution_count);
476 this.element.removeClass("running");
476 this.element.removeClass("running");
477 this.events.trigger('set_dirty.Notebook', {value: true});
477 this.events.trigger('set_dirty.Notebook', {value: true});
478 };
478 };
479
479
480 /**
480 /**
481 * @method _handle_set_next_input
481 * @method _handle_set_next_input
482 * @private
482 * @private
483 */
483 */
484 CodeCell.prototype._handle_set_next_input = function (payload) {
484 CodeCell.prototype._handle_set_next_input = function (payload) {
485 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
485 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
486 this.events.trigger('set_next_input.Notebook', data);
486 this.events.trigger('set_next_input.Notebook', data);
487 };
487 };
488
488
489 /**
489 /**
490 * @method _handle_input_request
490 * @method _handle_input_request
491 * @private
491 * @private
492 */
492 */
493 CodeCell.prototype._handle_input_request = function (msg) {
493 CodeCell.prototype._handle_input_request = function (msg) {
494 this.output_area.append_raw_input(msg);
494 this.output_area.append_raw_input(msg);
495 };
495 };
496
496
497
497
498 // Basic cell manipulation.
498 // Basic cell manipulation.
499
499
500 CodeCell.prototype.select = function () {
500 CodeCell.prototype.select = function () {
501 var cont = Cell.prototype.select.apply(this);
501 var cont = Cell.prototype.select.apply(this);
502 if (cont) {
502 if (cont) {
503 this.code_mirror.refresh();
503 this.code_mirror.refresh();
504 this.auto_highlight();
504 this.auto_highlight();
505 }
505 }
506 return cont;
506 return cont;
507 };
507 };
508
508
509 CodeCell.prototype.render = function () {
509 CodeCell.prototype.render = function () {
510 var cont = Cell.prototype.render.apply(this);
510 var cont = Cell.prototype.render.apply(this);
511 // Always execute, even if we are already in the rendered state
511 // Always execute, even if we are already in the rendered state
512 return cont;
512 return cont;
513 };
513 };
514
514
515 CodeCell.prototype.select_all = function () {
515 CodeCell.prototype.select_all = function () {
516 var start = {line: 0, ch: 0};
516 var start = {line: 0, ch: 0};
517 var nlines = this.code_mirror.lineCount();
517 var nlines = this.code_mirror.lineCount();
518 var last_line = this.code_mirror.getLine(nlines-1);
518 var last_line = this.code_mirror.getLine(nlines-1);
519 var end = {line: nlines-1, ch: last_line.length};
519 var end = {line: nlines-1, ch: last_line.length};
520 this.code_mirror.setSelection(start, end);
520 this.code_mirror.setSelection(start, end);
521 };
521 };
522
522
523
523
524 CodeCell.prototype.collapse_output = function () {
524 CodeCell.prototype.collapse_output = function () {
525 this.output_area.collapse();
525 this.output_area.collapse();
526 };
526 };
527
527
528
528
529 CodeCell.prototype.expand_output = function () {
529 CodeCell.prototype.expand_output = function () {
530 this.output_area.expand();
530 this.output_area.expand();
531 this.output_area.unscroll_area();
531 this.output_area.unscroll_area();
532 };
532 };
533
533
534 CodeCell.prototype.scroll_output = function () {
534 CodeCell.prototype.scroll_output = function () {
535 this.output_area.expand();
535 this.output_area.expand();
536 this.output_area.scroll_if_long();
536 this.output_area.scroll_if_long();
537 };
537 };
538
538
539 CodeCell.prototype.toggle_output = function () {
539 CodeCell.prototype.toggle_output = function () {
540 this.output_area.toggle_output();
540 this.output_area.toggle_output();
541 };
541 };
542
542
543 CodeCell.prototype.toggle_output_scroll = function () {
543 CodeCell.prototype.toggle_output_scroll = function () {
544 this.output_area.toggle_scroll();
544 this.output_area.toggle_scroll();
545 };
545 };
546
546
547
547
548 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
548 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
549 var ns;
549 var ns;
550 if (prompt_value === undefined || prompt_value === null) {
550 if (prompt_value === undefined || prompt_value === null) {
551 ns = "&nbsp;";
551 ns = "&nbsp;";
552 } else {
552 } else {
553 ns = encodeURIComponent(prompt_value);
553 ns = encodeURIComponent(prompt_value);
554 }
554 }
555 return 'In&nbsp;[' + ns + ']:';
555 return 'In&nbsp;[' + ns + ']:';
556 };
556 };
557
557
558 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
558 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
559 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
559 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
560 for(var i=1; i < lines_number; i++) {
560 for(var i=1; i < lines_number; i++) {
561 html.push(['...:']);
561 html.push(['...:']);
562 }
562 }
563 return html.join('<br/>');
563 return html.join('<br/>');
564 };
564 };
565
565
566 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
566 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
567
567
568
568
569 CodeCell.prototype.set_input_prompt = function (number) {
569 CodeCell.prototype.set_input_prompt = function (number) {
570 var nline = 1;
570 var nline = 1;
571 if (this.code_mirror !== undefined) {
571 if (this.code_mirror !== undefined) {
572 nline = this.code_mirror.lineCount();
572 nline = this.code_mirror.lineCount();
573 }
573 }
574 this.input_prompt_number = number;
574 this.input_prompt_number = number;
575 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
575 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
576 // This HTML call is okay because the user contents are escaped.
576 // This HTML call is okay because the user contents are escaped.
577 this.element.find('div.input_prompt').html(prompt_html);
577 this.element.find('div.input_prompt').html(prompt_html);
578 };
578 };
579
579
580
580
581 CodeCell.prototype.clear_input = function () {
581 CodeCell.prototype.clear_input = function () {
582 this.code_mirror.setValue('');
582 this.code_mirror.setValue('');
583 };
583 };
584
584
585
585
586 CodeCell.prototype.get_text = function () {
586 CodeCell.prototype.get_text = function () {
587 return this.code_mirror.getValue();
587 return this.code_mirror.getValue();
588 };
588 };
589
589
590
590
591 CodeCell.prototype.set_text = function (code) {
591 CodeCell.prototype.set_text = function (code) {
592 return this.code_mirror.setValue(code);
592 return this.code_mirror.setValue(code);
593 };
593 };
594
594
595
595
596 CodeCell.prototype.clear_output = function (wait) {
596 CodeCell.prototype.clear_output = function (wait) {
597 this.output_area.clear_output(wait);
597 this.output_area.clear_output(wait);
598 this.set_input_prompt();
598 this.set_input_prompt();
599 };
599 };
600
600
601
601
602 // JSON serialization
602 // JSON serialization
603
603
604 CodeCell.prototype.fromJSON = function (data) {
604 CodeCell.prototype.fromJSON = function (data) {
605 Cell.prototype.fromJSON.apply(this, arguments);
605 Cell.prototype.fromJSON.apply(this, arguments);
606 if (data.cell_type === 'code') {
606 if (data.cell_type === 'code') {
607 if (data.source !== undefined) {
607 if (data.source !== undefined) {
608 this.set_text(data.source);
608 this.set_text(data.source);
609 // make this value the starting point, so that we can only undo
609 // make this value the starting point, so that we can only undo
610 // to this state, instead of a blank cell
610 // to this state, instead of a blank cell
611 this.code_mirror.clearHistory();
611 this.code_mirror.clearHistory();
612 this.auto_highlight();
612 this.auto_highlight();
613 }
613 }
614 this.set_input_prompt(data.execution_count);
614 this.set_input_prompt(data.execution_count);
615 this.output_area.trusted = data.metadata.trusted || false;
615 this.output_area.trusted = data.metadata.trusted || false;
616 this.output_area.fromJSON(data.outputs, data.metadata);
616 this.output_area.fromJSON(data.outputs, data.metadata);
617 }
617 }
618 };
618 };
619
619
620
620
621 CodeCell.prototype.toJSON = function () {
621 CodeCell.prototype.toJSON = function () {
622 var data = Cell.prototype.toJSON.apply(this);
622 var data = Cell.prototype.toJSON.apply(this);
623 data.source = this.get_text();
623 data.source = this.get_text();
624 // is finite protect against undefined and '*' value
624 // is finite protect against undefined and '*' value
625 if (isFinite(this.input_prompt_number)) {
625 if (isFinite(this.input_prompt_number)) {
626 data.execution_count = this.input_prompt_number;
626 data.execution_count = this.input_prompt_number;
627 } else {
627 } else {
628 data.execution_count = null;
628 data.execution_count = null;
629 }
629 }
630 var outputs = this.output_area.toJSON();
630 var outputs = this.output_area.toJSON();
631 data.outputs = outputs;
631 data.outputs = outputs;
632 data.metadata.trusted = this.output_area.trusted;
632 data.metadata.trusted = this.output_area.trusted;
633 data.metadata.collapsed = this.output_area.collapsed;
633 data.metadata.collapsed = this.output_area.collapsed;
634 if (this.output_area.scroll_state === 'auto') {
634 if (this.output_area.scroll_state === 'auto') {
635 delete data.metadata.scrolled;
635 delete data.metadata.scrolled;
636 } else {
636 } else {
637 data.metadata.scrolled = this.output_area.scroll_state;
637 data.metadata.scrolled = this.output_area.scroll_state;
638 }
638 }
639 return data;
639 return data;
640 };
640 };
641
641
642 /**
642 /**
643 * handle cell level logic when a cell is unselected
643 * handle cell level logic when a cell is unselected
644 * @method unselect
644 * @method unselect
645 * @return is the action being taken
645 * @return is the action being taken
646 */
646 */
647 CodeCell.prototype.unselect = function () {
647 CodeCell.prototype.unselect = function () {
648 var cont = Cell.prototype.unselect.apply(this);
648 var cont = Cell.prototype.unselect.apply(this);
649 if (cont) {
649 if (cont) {
650 // When a code cell is usnelected, make sure that the corresponding
650 // When a code cell is usnelected, make sure that the corresponding
651 // tooltip and completer to that cell is closed.
651 // tooltip and completer to that cell is closed.
652 this.tooltip.remove_and_cancel_tooltip(true);
652 this.tooltip.remove_and_cancel_tooltip(true);
653 if (this.completer !== null) {
653 if (this.completer !== null) {
654 this.completer.close();
654 this.completer.close();
655 }
655 }
656 }
656 }
657 return cont;
657 return cont;
658 };
658 };
659
659
660 // Backwards compatability.
660 // Backwards compatability.
661 IPython.CodeCell = CodeCell;
661 IPython.CodeCell = CodeCell;
662
662
663 return {'CodeCell': CodeCell};
663 return {'CodeCell': CodeCell};
664 });
664 });
@@ -1,375 +1,374 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'base/js/utils',
6 'base/js/utils',
7 'jquery',
7 'jquery',
8 'notebook/js/cell',
8 'notebook/js/cell',
9 'base/js/security',
9 'base/js/security',
10 'services/config',
10 'services/config',
11 'notebook/js/mathjaxutils',
11 'notebook/js/mathjaxutils',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'codemirror/lib/codemirror',
14 'codemirror/lib/codemirror',
15 'codemirror/mode/gfm/gfm',
15 'codemirror/mode/gfm/gfm',
16 'notebook/js/codemirror-ipythongfm'
16 'notebook/js/codemirror-ipythongfm'
17 ], function(IPython,
17 ], function(IPython,
18 utils,
18 utils,
19 $,
19 $,
20 cell,
20 cell,
21 security,
21 security,
22 configmod,
22 configmod,
23 mathjaxutils,
23 mathjaxutils,
24 celltoolbar,
24 celltoolbar,
25 marked,
25 marked,
26 CodeMirror,
26 CodeMirror,
27 gfm,
27 gfm,
28 ipgfm
28 ipgfm
29 ) {
29 ) {
30 "use strict";
30 "use strict";
31 var Cell = cell.Cell;
31 var Cell = cell.Cell;
32
32
33 var TextCell = function (options) {
33 var TextCell = function (options) {
34 /**
34 /**
35 * Constructor
35 * Constructor
36 *
36 *
37 * Construct a new TextCell, codemirror mode is by default 'htmlmixed',
37 * Construct a new TextCell, codemirror mode is by default 'htmlmixed',
38 * and cell type is 'text' cell start as not redered.
38 * and cell type is 'text' cell start as not redered.
39 *
39 *
40 * Parameters:
40 * Parameters:
41 * options: dictionary
41 * options: dictionary
42 * Dictionary of keyword arguments.
42 * Dictionary of keyword arguments.
43 * events: $(Events) instance
43 * events: $(Events) instance
44 * config: dictionary
44 * config: dictionary
45 * keyboard_manager: KeyboardManager instance
45 * keyboard_manager: KeyboardManager instance
46 * notebook: Notebook instance
46 * notebook: Notebook instance
47 */
47 */
48 options = options || {};
48 options = options || {};
49
49
50 // in all TextCell/Cell subclasses
50 // in all TextCell/Cell subclasses
51 // do not assign most of members here, just pass it down
51 // do not assign most of members here, just pass it down
52 // in the options dict potentially overwriting what you wish.
52 // in the options dict potentially overwriting what you wish.
53 // they will be assigned in the base class.
53 // they will be assigned in the base class.
54 this.notebook = options.notebook;
54 this.notebook = options.notebook;
55 this.events = options.events;
55 this.events = options.events;
56 this.config = options.config;
56 this.config = options.config;
57
57
58 // we cannot put this as a class key as it has handle to "this".
58 // we cannot put this as a class key as it has handle to "this".
59 var config = utils.mergeopt(TextCell, this.config);
59 var config = utils.mergeopt(TextCell, this.config);
60 Cell.apply(this, [{
60 Cell.apply(this, [{
61 config: config,
61 config: config,
62 keyboard_manager: options.keyboard_manager,
62 keyboard_manager: options.keyboard_manager,
63 events: this.events}]);
63 events: this.events}]);
64
64
65 this.cell_type = this.cell_type || 'text';
65 this.cell_type = this.cell_type || 'text';
66 mathjaxutils = mathjaxutils;
66 mathjaxutils = mathjaxutils;
67 this.rendered = false;
67 this.rendered = false;
68 };
68 };
69
69
70 TextCell.prototype = Object.create(Cell.prototype);
70 TextCell.prototype = Object.create(Cell.prototype);
71
71
72 TextCell.options_default = {
72 TextCell.options_default = {
73 cm_config : {
73 cm_config : {
74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
75 mode: 'htmlmixed',
75 mode: 'htmlmixed',
76 lineWrapping : true,
76 lineWrapping : true,
77 }
77 }
78 };
78 };
79
79
80
80
81 /**
81 /**
82 * Create the DOM element of the TextCell
82 * Create the DOM element of the TextCell
83 * @method create_element
83 * @method create_element
84 * @private
84 * @private
85 */
85 */
86 TextCell.prototype.create_element = function () {
86 TextCell.prototype.create_element = function () {
87 Cell.prototype.create_element.apply(this, arguments);
87 Cell.prototype.create_element.apply(this, arguments);
88 var that = this;
88 var that = this;
89
89
90 var cell = $("<div>").addClass('cell text_cell');
90 var cell = $("<div>").addClass('cell text_cell');
91 cell.attr('tabindex','2');
91 cell.attr('tabindex','2');
92
92
93 var prompt = $('<div/>').addClass('prompt input_prompt');
93 var prompt = $('<div/>').addClass('prompt input_prompt');
94 cell.append(prompt);
94 cell.append(prompt);
95 var inner_cell = $('<div/>').addClass('inner_cell');
95 var inner_cell = $('<div/>').addClass('inner_cell');
96 this.celltoolbar = new celltoolbar.CellToolbar({
96 this.celltoolbar = new celltoolbar.CellToolbar({
97 cell: this,
97 cell: this,
98 notebook: this.notebook});
98 notebook: this.notebook});
99 inner_cell.append(this.celltoolbar.element);
99 inner_cell.append(this.celltoolbar.element);
100 var input_area = $('<div/>').addClass('input_area');
100 var input_area = $('<div/>').addClass('input_area');
101 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
101 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
102 // In case of bugs that put the keyboard manager into an inconsistent state,
102 // In case of bugs that put the keyboard manager into an inconsistent state,
103 // ensure KM is enabled when CodeMirror is focused:
103 // ensure KM is enabled when CodeMirror is focused:
104 this.code_mirror.on('focus', function () {
104 this.code_mirror.on('focus', function () {
105 if (that.keyboard_manager) {
105 if (that.keyboard_manager) {
106 that.keyboard_manager.enable();
106 that.keyboard_manager.enable();
107 }
107 }
108 });
108 });
109 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
109 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
110 // The tabindex=-1 makes this div focusable.
110 // The tabindex=-1 makes this div focusable.
111 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
111 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
112 .attr('tabindex','-1');
112 .attr('tabindex','-1');
113 inner_cell.append(input_area).append(render_area);
113 inner_cell.append(input_area).append(render_area);
114 cell.append(inner_cell);
114 cell.append(inner_cell);
115 this.element = cell;
115 this.element = cell;
116 };
116 };
117
117
118
118
119 // Cell level actions
119 // Cell level actions
120
120
121 TextCell.prototype.select = function () {
121 TextCell.prototype.select = function () {
122 var cont = Cell.prototype.select.apply(this);
122 var cont = Cell.prototype.select.apply(this);
123 if (cont) {
123 if (cont) {
124 if (this.mode === 'edit') {
124 if (this.mode === 'edit') {
125 this.code_mirror.refresh();
125 this.code_mirror.refresh();
126 }
126 }
127 }
127 }
128 return cont;
128 return cont;
129 };
129 };
130
130
131 TextCell.prototype.unrender = function () {
131 TextCell.prototype.unrender = function () {
132 if (this.read_only) return;
133 var cont = Cell.prototype.unrender.apply(this);
132 var cont = Cell.prototype.unrender.apply(this);
134 if (cont) {
133 if (cont) {
135 var text_cell = this.element;
134 var text_cell = this.element;
136 if (this.get_text() === this.placeholder) {
135 if (this.get_text() === this.placeholder) {
137 this.set_text('');
136 this.set_text('');
138 }
137 }
139 this.refresh();
138 this.refresh();
140 }
139 }
141 return cont;
140 return cont;
142 };
141 };
143
142
144 TextCell.prototype.execute = function () {
143 TextCell.prototype.execute = function () {
145 this.render();
144 this.render();
146 };
145 };
147
146
148 /**
147 /**
149 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
148 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
150 * @method get_text
149 * @method get_text
151 * @retrun {string} CodeMirror current text value
150 * @retrun {string} CodeMirror current text value
152 */
151 */
153 TextCell.prototype.get_text = function() {
152 TextCell.prototype.get_text = function() {
154 return this.code_mirror.getValue();
153 return this.code_mirror.getValue();
155 };
154 };
156
155
157 /**
156 /**
158 * @param {string} text - Codemiror text value
157 * @param {string} text - Codemiror text value
159 * @see TextCell#get_text
158 * @see TextCell#get_text
160 * @method set_text
159 * @method set_text
161 * */
160 * */
162 TextCell.prototype.set_text = function(text) {
161 TextCell.prototype.set_text = function(text) {
163 this.code_mirror.setValue(text);
162 this.code_mirror.setValue(text);
164 this.unrender();
163 this.unrender();
165 this.code_mirror.refresh();
164 this.code_mirror.refresh();
166 };
165 };
167
166
168 /**
167 /**
169 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
168 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
170 * @method get_rendered
169 * @method get_rendered
171 * */
170 * */
172 TextCell.prototype.get_rendered = function() {
171 TextCell.prototype.get_rendered = function() {
173 return this.element.find('div.text_cell_render').html();
172 return this.element.find('div.text_cell_render').html();
174 };
173 };
175
174
176 /**
175 /**
177 * @method set_rendered
176 * @method set_rendered
178 */
177 */
179 TextCell.prototype.set_rendered = function(text) {
178 TextCell.prototype.set_rendered = function(text) {
180 this.element.find('div.text_cell_render').html(text);
179 this.element.find('div.text_cell_render').html(text);
181 };
180 };
182
181
183
182
184 /**
183 /**
185 * Create Text cell from JSON
184 * Create Text cell from JSON
186 * @param {json} data - JSON serialized text-cell
185 * @param {json} data - JSON serialized text-cell
187 * @method fromJSON
186 * @method fromJSON
188 */
187 */
189 TextCell.prototype.fromJSON = function (data) {
188 TextCell.prototype.fromJSON = function (data) {
190 Cell.prototype.fromJSON.apply(this, arguments);
189 Cell.prototype.fromJSON.apply(this, arguments);
191 if (data.cell_type === this.cell_type) {
190 if (data.cell_type === this.cell_type) {
192 if (data.source !== undefined) {
191 if (data.source !== undefined) {
193 this.set_text(data.source);
192 this.set_text(data.source);
194 // make this value the starting point, so that we can only undo
193 // make this value the starting point, so that we can only undo
195 // to this state, instead of a blank cell
194 // to this state, instead of a blank cell
196 this.code_mirror.clearHistory();
195 this.code_mirror.clearHistory();
197 // TODO: This HTML needs to be treated as potentially dangerous
196 // TODO: This HTML needs to be treated as potentially dangerous
198 // user input and should be handled before set_rendered.
197 // user input and should be handled before set_rendered.
199 this.set_rendered(data.rendered || '');
198 this.set_rendered(data.rendered || '');
200 this.rendered = false;
199 this.rendered = false;
201 this.render();
200 this.render();
202 }
201 }
203 }
202 }
204 };
203 };
205
204
206 /** Generate JSON from cell
205 /** Generate JSON from cell
207 * @return {object} cell data serialised to json
206 * @return {object} cell data serialised to json
208 */
207 */
209 TextCell.prototype.toJSON = function () {
208 TextCell.prototype.toJSON = function () {
210 var data = Cell.prototype.toJSON.apply(this);
209 var data = Cell.prototype.toJSON.apply(this);
211 data.source = this.get_text();
210 data.source = this.get_text();
212 if (data.source == this.placeholder) {
211 if (data.source == this.placeholder) {
213 data.source = "";
212 data.source = "";
214 }
213 }
215 return data;
214 return data;
216 };
215 };
217
216
218
217
219 var MarkdownCell = function (options) {
218 var MarkdownCell = function (options) {
220 /**
219 /**
221 * Constructor
220 * Constructor
222 *
221 *
223 * Parameters:
222 * Parameters:
224 * options: dictionary
223 * options: dictionary
225 * Dictionary of keyword arguments.
224 * Dictionary of keyword arguments.
226 * events: $(Events) instance
225 * events: $(Events) instance
227 * config: ConfigSection instance
226 * config: ConfigSection instance
228 * keyboard_manager: KeyboardManager instance
227 * keyboard_manager: KeyboardManager instance
229 * notebook: Notebook instance
228 * notebook: Notebook instance
230 */
229 */
231 options = options || {};
230 options = options || {};
232 var config = utils.mergeopt(MarkdownCell, {});
231 var config = utils.mergeopt(MarkdownCell, {});
233 TextCell.apply(this, [$.extend({}, options, {config: config})]);
234
235 this.class_config = new configmod.ConfigWithDefaults(options.config,
232 this.class_config = new configmod.ConfigWithDefaults(options.config,
236 {}, 'MarkdownCell');
233 {}, 'MarkdownCell');
234 TextCell.apply(this, [$.extend({}, options, {config: config})]);
235
237 this.cell_type = 'markdown';
236 this.cell_type = 'markdown';
238 };
237 };
239
238
240 MarkdownCell.options_default = {
239 MarkdownCell.options_default = {
241 cm_config: {
240 cm_config: {
242 mode: 'ipythongfm'
241 mode: 'ipythongfm'
243 },
242 },
244 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
243 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
245 };
244 };
246
245
247 MarkdownCell.prototype = Object.create(TextCell.prototype);
246 MarkdownCell.prototype = Object.create(TextCell.prototype);
248
247
249 MarkdownCell.prototype.set_heading_level = function (level) {
248 MarkdownCell.prototype.set_heading_level = function (level) {
250 /**
249 /**
251 * make a markdown cell a heading
250 * make a markdown cell a heading
252 */
251 */
253 level = level || 1;
252 level = level || 1;
254 var source = this.get_text();
253 var source = this.get_text();
255 source = source.replace(/^(#*)\s?/,
254 source = source.replace(/^(#*)\s?/,
256 new Array(level + 1).join('#') + ' ');
255 new Array(level + 1).join('#') + ' ');
257 this.set_text(source);
256 this.set_text(source);
258 this.refresh();
257 this.refresh();
259 if (this.rendered) {
258 if (this.rendered) {
260 this.render();
259 this.render();
261 }
260 }
262 };
261 };
263
262
264 /**
263 /**
265 * @method render
264 * @method render
266 */
265 */
267 MarkdownCell.prototype.render = function () {
266 MarkdownCell.prototype.render = function () {
268 var cont = TextCell.prototype.render.apply(this);
267 var cont = TextCell.prototype.render.apply(this);
269 if (cont) {
268 if (cont) {
270 var that = this;
269 var that = this;
271 var text = this.get_text();
270 var text = this.get_text();
272 var math = null;
271 var math = null;
273 if (text === "") { text = this.placeholder; }
272 if (text === "") { text = this.placeholder; }
274 var text_and_math = mathjaxutils.remove_math(text);
273 var text_and_math = mathjaxutils.remove_math(text);
275 text = text_and_math[0];
274 text = text_and_math[0];
276 math = text_and_math[1];
275 math = text_and_math[1];
277 marked(text, function (err, html) {
276 marked(text, function (err, html) {
278 html = mathjaxutils.replace_math(html, math);
277 html = mathjaxutils.replace_math(html, math);
279 html = security.sanitize_html(html);
278 html = security.sanitize_html(html);
280 html = $($.parseHTML(html));
279 html = $($.parseHTML(html));
281 // add anchors to headings
280 // add anchors to headings
282 html.find(":header").addBack(":header").each(function (i, h) {
281 html.find(":header").addBack(":header").each(function (i, h) {
283 h = $(h);
282 h = $(h);
284 var hash = h.text().replace(/ /g, '-');
283 var hash = h.text().replace(/ /g, '-');
285 h.attr('id', hash);
284 h.attr('id', hash);
286 h.append(
285 h.append(
287 $('<a/>')
286 $('<a/>')
288 .addClass('anchor-link')
287 .addClass('anchor-link')
289 .attr('href', '#' + hash)
288 .attr('href', '#' + hash)
290 .text('ΒΆ')
289 .text('ΒΆ')
291 );
290 );
292 });
291 });
293 // links in markdown cells should open in new tabs
292 // links in markdown cells should open in new tabs
294 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
293 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
295 that.set_rendered(html);
294 that.set_rendered(html);
296 that.typeset();
295 that.typeset();
297 that.events.trigger("rendered.MarkdownCell", {cell: that});
296 that.events.trigger("rendered.MarkdownCell", {cell: that});
298 });
297 });
299 }
298 }
300 return cont;
299 return cont;
301 };
300 };
302
301
303
302
304 var RawCell = function (options) {
303 var RawCell = function (options) {
305 /**
304 /**
306 * Constructor
305 * Constructor
307 *
306 *
308 * Parameters:
307 * Parameters:
309 * options: dictionary
308 * options: dictionary
310 * Dictionary of keyword arguments.
309 * Dictionary of keyword arguments.
311 * events: $(Events) instance
310 * events: $(Events) instance
312 * config: ConfigSection instance
311 * config: ConfigSection instance
313 * keyboard_manager: KeyboardManager instance
312 * keyboard_manager: KeyboardManager instance
314 * notebook: Notebook instance
313 * notebook: Notebook instance
315 */
314 */
316 options = options || {};
315 options = options || {};
317 var config = utils.mergeopt(RawCell, {});
316 var config = utils.mergeopt(RawCell, {});
318 TextCell.apply(this, [$.extend({}, options, {config: config})]);
317 TextCell.apply(this, [$.extend({}, options, {config: config})]);
319
318
320 this.class_config = new configmod.ConfigWithDefaults(options.config,
319 this.class_config = new configmod.ConfigWithDefaults(options.config,
321 RawCell.config_defaults, 'RawCell');
320 RawCell.config_defaults, 'RawCell');
322 this.cell_type = 'raw';
321 this.cell_type = 'raw';
323 };
322 };
324
323
325 RawCell.options_default = {
324 RawCell.options_default = {
326 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
325 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
327 "It will not be rendered in the notebook. " +
326 "It will not be rendered in the notebook. " +
328 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
327 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
329 };
328 };
330
329
331 RawCell.config_defaults = {
330 RawCell.config_defaults = {
332 highlight_modes : {
331 highlight_modes : {
333 'diff' :{'reg':[/^diff/]}
332 'diff' :{'reg':[/^diff/]}
334 },
333 },
335 };
334 };
336
335
337 RawCell.prototype = Object.create(TextCell.prototype);
336 RawCell.prototype = Object.create(TextCell.prototype);
338
337
339 /** @method bind_events **/
338 /** @method bind_events **/
340 RawCell.prototype.bind_events = function () {
339 RawCell.prototype.bind_events = function () {
341 TextCell.prototype.bind_events.apply(this);
340 TextCell.prototype.bind_events.apply(this);
342 var that = this;
341 var that = this;
343 this.element.focusout(function() {
342 this.element.focusout(function() {
344 that.auto_highlight();
343 that.auto_highlight();
345 that.render();
344 that.render();
346 });
345 });
347
346
348 this.code_mirror.on('focus', function() { that.unrender(); });
347 this.code_mirror.on('focus', function() { that.unrender(); });
349 };
348 };
350
349
351 /** @method render **/
350 /** @method render **/
352 RawCell.prototype.render = function () {
351 RawCell.prototype.render = function () {
353 var cont = TextCell.prototype.render.apply(this);
352 var cont = TextCell.prototype.render.apply(this);
354 if (cont){
353 if (cont){
355 var text = this.get_text();
354 var text = this.get_text();
356 if (text === "") { text = this.placeholder; }
355 if (text === "") { text = this.placeholder; }
357 this.set_text(text);
356 this.set_text(text);
358 this.element.removeClass('rendered');
357 this.element.removeClass('rendered');
359 this.auto_highlight();
358 this.auto_highlight();
360 }
359 }
361 return cont;
360 return cont;
362 };
361 };
363
362
364 // Backwards compatability.
363 // Backwards compatability.
365 IPython.TextCell = TextCell;
364 IPython.TextCell = TextCell;
366 IPython.MarkdownCell = MarkdownCell;
365 IPython.MarkdownCell = MarkdownCell;
367 IPython.RawCell = RawCell;
366 IPython.RawCell = RawCell;
368
367
369 var textcell = {
368 var textcell = {
370 TextCell: TextCell,
369 TextCell: TextCell,
371 MarkdownCell: MarkdownCell,
370 MarkdownCell: MarkdownCell,
372 RawCell: RawCell
371 RawCell: RawCell
373 };
372 };
374 return textcell;
373 return textcell;
375 });
374 });
General Comments 0
You need to be logged in to leave comments. Login now