##// END OF EJS Templates
Generalise render/unrender to all cells type...
Matthias BUSSONNIER -
Show More
@@ -1,557 +1,567 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 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 // TODO: remove IPython dependency here
9 // TODO: remove IPython dependency here
10 "use strict";
10 "use strict";
11
11
12 // monkey patch CM to be able to syntax highlight cell magics
12 // monkey patch CM to be able to syntax highlight cell magics
13 // bug reported upstream,
13 // bug reported upstream,
14 // see https://github.com/codemirror/CodeMirror/issues/670
14 // see https://github.com/codemirror/CodeMirror/issues/670
15 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
15 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
16 CodeMirror.modes.null = function() {
16 CodeMirror.modes.null = function() {
17 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
17 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
18 };
18 };
19 }
19 }
20
20
21 CodeMirror.patchedGetMode = function(config, mode){
21 CodeMirror.patchedGetMode = function(config, mode){
22 var cmmode = CodeMirror.getMode(config, mode);
22 var cmmode = CodeMirror.getMode(config, mode);
23 if(cmmode.indent === null) {
23 if(cmmode.indent === null) {
24 console.log('patch mode "' , mode, '" on the fly');
24 console.log('patch mode "' , mode, '" on the fly');
25 cmmode.indent = function(){return 0;};
25 cmmode.indent = function(){return 0;};
26 }
26 }
27 return cmmode;
27 return cmmode;
28 };
28 };
29 // end monkey patching CodeMirror
29 // end monkey patching CodeMirror
30
30
31 var Cell = function (options) {
31 var Cell = function (options) {
32 // Constructor
32 // Constructor
33 //
33 //
34 // The Base `Cell` class from which to inherit.
34 // The Base `Cell` class from which to inherit.
35 //
35 //
36 // Parameters:
36 // Parameters:
37 // options: dictionary
37 // options: dictionary
38 // Dictionary of keyword arguments.
38 // Dictionary of keyword arguments.
39 // events: $(Events) instance
39 // events: $(Events) instance
40 // config: dictionary
40 // config: dictionary
41 // keyboard_manager: KeyboardManager instance
41 // keyboard_manager: KeyboardManager instance
42 options = options || {};
42 options = options || {};
43 this.keyboard_manager = options.keyboard_manager;
43 this.keyboard_manager = options.keyboard_manager;
44 this.events = options.events;
44 this.events = options.events;
45 var config = utils.mergeopt(Cell, options.config);
45 var config = utils.mergeopt(Cell, options.config);
46 // superclass default overwrite our default
46 // superclass default overwrite our default
47
47
48 this.placeholder = config.placeholder || '';
48 this.placeholder = config.placeholder || '';
49 this.read_only = config.cm_config.readOnly;
49 this.read_only = config.cm_config.readOnly;
50 this.selected = false;
50 this.selected = false;
51 this.rendered = false;
51 this.rendered = false;
52 this.mode = 'command';
52 this.mode = 'command';
53 this.metadata = {};
53 this.metadata = {};
54 // load this from metadata later ?
54 // load this from metadata later ?
55 this.user_highlight = 'auto';
55 this.user_highlight = 'auto';
56 this.cm_config = config.cm_config;
56 this.cm_config = config.cm_config;
57 this.cell_id = utils.uuid();
57 this.cell_id = utils.uuid();
58 this._options = config;
58 this._options = config;
59
59
60 // For JS VM engines optimization, attributes should be all set (even
60 // For JS VM engines optimization, attributes should be all set (even
61 // to null) in the constructor, and if possible, if different subclass
61 // to null) in the constructor, and if possible, if different subclass
62 // have new attributes with same name, they should be created in the
62 // have new attributes with same name, they should be created in the
63 // same order. Easiest is to create and set to null in parent class.
63 // same order. Easiest is to create and set to null in parent class.
64
64
65 this.element = null;
65 this.element = null;
66 this.cell_type = this.cell_type || null;
66 this.cell_type = this.cell_type || null;
67 this.code_mirror = null;
67 this.code_mirror = null;
68
68
69 this.create_element();
69 this.create_element();
70 if (this.element !== null) {
70 if (this.element !== null) {
71 this.element.data("cell", this);
71 this.element.data("cell", this);
72 this.bind_events();
72 this.bind_events();
73 this.init_classes();
73 this.init_classes();
74 }
74 }
75 };
75 };
76
76
77 Cell.options_default = {
77 Cell.options_default = {
78 cm_config : {
78 cm_config : {
79 indentUnit : 4,
79 indentUnit : 4,
80 readOnly: false,
80 readOnly: false,
81 theme: "default",
81 theme: "default",
82 extraKeys: {
82 extraKeys: {
83 "Cmd-Right":"goLineRight",
83 "Cmd-Right":"goLineRight",
84 "End":"goLineRight",
84 "End":"goLineRight",
85 "Cmd-Left":"goLineLeft"
85 "Cmd-Left":"goLineLeft"
86 }
86 }
87 }
87 }
88 };
88 };
89
89
90 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
90 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
91 // by disabling drag/drop altogether on Safari
91 // by disabling drag/drop altogether on Safari
92 // https://github.com/codemirror/CodeMirror/issues/332
92 // https://github.com/codemirror/CodeMirror/issues/332
93 if (utils.browser[0] == "Safari") {
93 if (utils.browser[0] == "Safari") {
94 Cell.options_default.cm_config.dragDrop = false;
94 Cell.options_default.cm_config.dragDrop = false;
95 }
95 }
96
96
97 /**
97 /**
98 * Empty. Subclasses must implement create_element.
98 * Empty. Subclasses must implement create_element.
99 * This should contain all the code to create the DOM element in notebook
99 * This should contain all the code to create the DOM element in notebook
100 * and will be called by Base Class constructor.
100 * and will be called by Base Class constructor.
101 * @method create_element
101 * @method create_element
102 */
102 */
103 Cell.prototype.create_element = function () {
103 Cell.prototype.create_element = function () {
104 };
104 };
105
105
106 Cell.prototype.init_classes = function () {
106 Cell.prototype.init_classes = function () {
107 // Call after this.element exists to initialize the css classes
107 // Call after this.element exists to initialize the css classes
108 // related to selected, rendered and mode.
108 // related to selected, rendered and mode.
109 if (this.selected) {
109 if (this.selected) {
110 this.element.addClass('selected');
110 this.element.addClass('selected');
111 } else {
111 } else {
112 this.element.addClass('unselected');
112 this.element.addClass('unselected');
113 }
113 }
114 if (this.rendered) {
114 if (this.rendered) {
115 this.element.addClass('rendered');
115 this.element.addClass('rendered');
116 } else {
116 } else {
117 this.element.addClass('unrendered');
117 this.element.addClass('unrendered');
118 }
118 }
119 if (this.mode === 'edit') {
119 if (this.mode === 'edit') {
120 this.element.addClass('edit_mode');
120 this.element.addClass('edit_mode');
121 } else {
121 } else {
122 this.element.addClass('command_mode');
122 this.element.addClass('command_mode');
123 }
123 }
124 };
124 };
125
125
126 /**
126 /**
127 * Subclasses can implement override bind_events.
127 * Subclasses can implement override bind_events.
128 * Be carefull to call the parent method when overwriting as it fires event.
128 * Be carefull to call the parent method when overwriting as it fires event.
129 * this will be triggerd after create_element in constructor.
129 * this will be triggerd after create_element in constructor.
130 * @method bind_events
130 * @method bind_events
131 */
131 */
132 Cell.prototype.bind_events = function () {
132 Cell.prototype.bind_events = function () {
133 var that = this;
133 var that = this;
134 // We trigger events so that Cell doesn't have to depend on Notebook.
134 // We trigger events so that Cell doesn't have to depend on Notebook.
135 that.element.click(function (event) {
135 that.element.click(function (event) {
136 if (!that.selected) {
136 if (!that.selected) {
137 that.events.trigger('select.Cell', {'cell':that});
137 that.events.trigger('select.Cell', {'cell':that});
138 }
138 }
139 });
139 });
140 that.element.focusin(function (event) {
140 that.element.focusin(function (event) {
141 if (!that.selected) {
141 if (!that.selected) {
142 that.events.trigger('select.Cell', {'cell':that});
142 that.events.trigger('select.Cell', {'cell':that});
143 }
143 }
144 });
144 });
145 if (this.code_mirror) {
145 if (this.code_mirror) {
146 this.code_mirror.on("change", function(cm, change) {
146 this.code_mirror.on("change", function(cm, change) {
147 that.events.trigger("set_dirty.Notebook", {value: true});
147 that.events.trigger("set_dirty.Notebook", {value: true});
148 });
148 });
149 }
149 }
150 if (this.code_mirror) {
150 if (this.code_mirror) {
151 this.code_mirror.on('focus', function(cm, change) {
151 this.code_mirror.on('focus', function(cm, change) {
152 that.events.trigger('edit_mode.Cell', {cell: that});
152 that.events.trigger('edit_mode.Cell', {cell: that});
153 });
153 });
154 }
154 }
155 if (this.code_mirror) {
155 if (this.code_mirror) {
156 this.code_mirror.on('blur', function(cm, change) {
156 this.code_mirror.on('blur', function(cm, change) {
157 that.events.trigger('command_mode.Cell', {cell: that});
157 that.events.trigger('command_mode.Cell', {cell: that});
158 });
158 });
159 }
159 }
160
161 this.element.dblclick(function () {
162 if (that.selected === false) {
163 this.events.trigger('select.Cell', {'cell':that});
164 }
165 var cont = that.unrender();
166 if (cont) {
167 that.focus_editor();
168 }
169 });
160 };
170 };
161
171
162 /**
172 /**
163 * This method gets called in CodeMirror's onKeyDown/onKeyPress
173 * This method gets called in CodeMirror's onKeyDown/onKeyPress
164 * handlers and is used to provide custom key handling.
174 * handlers and is used to provide custom key handling.
165 *
175 *
166 * To have custom handling, subclasses should override this method, but still call it
176 * To have custom handling, subclasses should override this method, but still call it
167 * in order to process the Edit mode keyboard shortcuts.
177 * in order to process the Edit mode keyboard shortcuts.
168 *
178 *
169 * @method handle_codemirror_keyevent
179 * @method handle_codemirror_keyevent
170 * @param {CodeMirror} editor - The codemirror instance bound to the cell
180 * @param {CodeMirror} editor - The codemirror instance bound to the cell
171 * @param {event} event - key press event which either should or should not be handled by CodeMirror
181 * @param {event} event - key press event which either should or should not be handled by CodeMirror
172 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
182 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
173 */
183 */
174 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
184 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
175 var shortcuts = this.keyboard_manager.edit_shortcuts;
185 var shortcuts = this.keyboard_manager.edit_shortcuts;
176
186
177 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
187 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
178 // manager will handle it
188 // manager will handle it
179 if (shortcuts.handles(event)) { return true; }
189 if (shortcuts.handles(event)) { return true; }
180
190
181 return false;
191 return false;
182 };
192 };
183
193
184
194
185 /**
195 /**
186 * Triger typsetting of math by mathjax on current cell element
196 * Triger typsetting of math by mathjax on current cell element
187 * @method typeset
197 * @method typeset
188 */
198 */
189 Cell.prototype.typeset = function () {
199 Cell.prototype.typeset = function () {
190 if (window.MathJax) {
200 if (window.MathJax) {
191 var cell_math = this.element.get(0);
201 var cell_math = this.element.get(0);
192 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
202 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
193 }
203 }
194 };
204 };
195
205
196 /**
206 /**
197 * handle cell level logic when a cell is selected
207 * handle cell level logic when a cell is selected
198 * @method select
208 * @method select
199 * @return is the action being taken
209 * @return is the action being taken
200 */
210 */
201 Cell.prototype.select = function () {
211 Cell.prototype.select = function () {
202 if (!this.selected) {
212 if (!this.selected) {
203 this.element.addClass('selected');
213 this.element.addClass('selected');
204 this.element.removeClass('unselected');
214 this.element.removeClass('unselected');
205 this.selected = true;
215 this.selected = true;
206 return true;
216 return true;
207 } else {
217 } else {
208 return false;
218 return false;
209 }
219 }
210 };
220 };
211
221
212 /**
222 /**
213 * handle cell level logic when a cell is unselected
223 * handle cell level logic when a cell is unselected
214 * @method unselect
224 * @method unselect
215 * @return is the action being taken
225 * @return is the action being taken
216 */
226 */
217 Cell.prototype.unselect = function () {
227 Cell.prototype.unselect = function () {
218 if (this.selected) {
228 if (this.selected) {
219 this.element.addClass('unselected');
229 this.element.addClass('unselected');
220 this.element.removeClass('selected');
230 this.element.removeClass('selected');
221 this.selected = false;
231 this.selected = false;
222 return true;
232 return true;
223 } else {
233 } else {
224 return false;
234 return false;
225 }
235 }
226 };
236 };
227
237
228 /**
238 /**
229 * handle cell level logic when a cell is rendered
239 * handle cell level logic when a cell is rendered
230 * @method render
240 * @method render
231 * @return is the action being taken
241 * @return is the action being taken
232 */
242 */
233 Cell.prototype.render = function () {
243 Cell.prototype.render = function () {
234 if (!this.rendered) {
244 if (!this.rendered) {
235 this.element.addClass('rendered');
245 this.element.addClass('rendered');
236 this.element.removeClass('unrendered');
246 this.element.removeClass('unrendered');
237 this.rendered = true;
247 this.rendered = true;
238 return true;
248 return true;
239 } else {
249 } else {
240 return false;
250 return false;
241 }
251 }
242 };
252 };
243
253
244 /**
254 /**
245 * handle cell level logic when a cell is unrendered
255 * handle cell level logic when a cell is unrendered
246 * @method unrender
256 * @method unrender
247 * @return is the action being taken
257 * @return is the action being taken
248 */
258 */
249 Cell.prototype.unrender = function () {
259 Cell.prototype.unrender = function () {
250 if (this.rendered) {
260 if (this.rendered) {
251 this.element.addClass('unrendered');
261 this.element.addClass('unrendered');
252 this.element.removeClass('rendered');
262 this.element.removeClass('rendered');
253 this.rendered = false;
263 this.rendered = false;
254 return true;
264 return true;
255 } else {
265 } else {
256 return false;
266 return false;
257 }
267 }
258 };
268 };
259
269
260 /**
270 /**
261 * Delegates keyboard shortcut handling to either IPython keyboard
271 * Delegates keyboard shortcut handling to either IPython keyboard
262 * manager when in command mode, or CodeMirror when in edit mode
272 * manager when in command mode, or CodeMirror when in edit mode
263 *
273 *
264 * @method handle_keyevent
274 * @method handle_keyevent
265 * @param {CodeMirror} editor - The codemirror instance bound to the cell
275 * @param {CodeMirror} editor - The codemirror instance bound to the cell
266 * @param {event} - key event to be handled
276 * @param {event} - key event to be handled
267 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
277 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
268 */
278 */
269 Cell.prototype.handle_keyevent = function (editor, event) {
279 Cell.prototype.handle_keyevent = function (editor, event) {
270
280
271 // console.log('CM', this.mode, event.which, event.type)
281 // console.log('CM', this.mode, event.which, event.type)
272
282
273 if (this.mode === 'command') {
283 if (this.mode === 'command') {
274 return true;
284 return true;
275 } else if (this.mode === 'edit') {
285 } else if (this.mode === 'edit') {
276 return this.handle_codemirror_keyevent(editor, event);
286 return this.handle_codemirror_keyevent(editor, event);
277 }
287 }
278 };
288 };
279
289
280 /**
290 /**
281 * @method at_top
291 * @method at_top
282 * @return {Boolean}
292 * @return {Boolean}
283 */
293 */
284 Cell.prototype.at_top = function () {
294 Cell.prototype.at_top = function () {
285 var cm = this.code_mirror;
295 var cm = this.code_mirror;
286 var cursor = cm.getCursor();
296 var cursor = cm.getCursor();
287 if (cursor.line === 0 && cursor.ch === 0) {
297 if (cursor.line === 0 && cursor.ch === 0) {
288 return true;
298 return true;
289 }
299 }
290 return false;
300 return false;
291 };
301 };
292
302
293 /**
303 /**
294 * @method at_bottom
304 * @method at_bottom
295 * @return {Boolean}
305 * @return {Boolean}
296 * */
306 * */
297 Cell.prototype.at_bottom = function () {
307 Cell.prototype.at_bottom = function () {
298 var cm = this.code_mirror;
308 var cm = this.code_mirror;
299 var cursor = cm.getCursor();
309 var cursor = cm.getCursor();
300 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
310 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
301 return true;
311 return true;
302 }
312 }
303 return false;
313 return false;
304 };
314 };
305
315
306 /**
316 /**
307 * enter the command mode for the cell
317 * enter the command mode for the cell
308 * @method command_mode
318 * @method command_mode
309 * @return is the action being taken
319 * @return is the action being taken
310 */
320 */
311 Cell.prototype.command_mode = function () {
321 Cell.prototype.command_mode = function () {
312 if (this.mode !== 'command') {
322 if (this.mode !== 'command') {
313 this.element.addClass('command_mode');
323 this.element.addClass('command_mode');
314 this.element.removeClass('edit_mode');
324 this.element.removeClass('edit_mode');
315 this.mode = 'command';
325 this.mode = 'command';
316 return true;
326 return true;
317 } else {
327 } else {
318 return false;
328 return false;
319 }
329 }
320 };
330 };
321
331
322 /**
332 /**
323 * enter the edit mode for the cell
333 * enter the edit mode for the cell
324 * @method command_mode
334 * @method command_mode
325 * @return is the action being taken
335 * @return is the action being taken
326 */
336 */
327 Cell.prototype.edit_mode = function () {
337 Cell.prototype.edit_mode = function () {
328 if (this.mode !== 'edit') {
338 if (this.mode !== 'edit') {
329 this.element.addClass('edit_mode');
339 this.element.addClass('edit_mode');
330 this.element.removeClass('command_mode');
340 this.element.removeClass('command_mode');
331 this.mode = 'edit';
341 this.mode = 'edit';
332 return true;
342 return true;
333 } else {
343 } else {
334 return false;
344 return false;
335 }
345 }
336 };
346 };
337
347
338 /**
348 /**
339 * Focus the cell in the DOM sense
349 * Focus the cell in the DOM sense
340 * @method focus_cell
350 * @method focus_cell
341 */
351 */
342 Cell.prototype.focus_cell = function () {
352 Cell.prototype.focus_cell = function () {
343 this.element.focus();
353 this.element.focus();
344 };
354 };
345
355
346 /**
356 /**
347 * Focus the editor area so a user can type
357 * Focus the editor area so a user can type
348 *
358 *
349 * NOTE: If codemirror is focused via a mouse click event, you don't want to
359 * NOTE: If codemirror is focused via a mouse click event, you don't want to
350 * call this because it will cause a page jump.
360 * call this because it will cause a page jump.
351 * @method focus_editor
361 * @method focus_editor
352 */
362 */
353 Cell.prototype.focus_editor = function () {
363 Cell.prototype.focus_editor = function () {
354 this.refresh();
364 this.refresh();
355 this.code_mirror.focus();
365 this.code_mirror.focus();
356 };
366 };
357
367
358 /**
368 /**
359 * Refresh codemirror instance
369 * Refresh codemirror instance
360 * @method refresh
370 * @method refresh
361 */
371 */
362 Cell.prototype.refresh = function () {
372 Cell.prototype.refresh = function () {
363 this.code_mirror.refresh();
373 this.code_mirror.refresh();
364 };
374 };
365
375
366 /**
376 /**
367 * should be overritten by subclass
377 * should be overritten by subclass
368 * @method get_text
378 * @method get_text
369 */
379 */
370 Cell.prototype.get_text = function () {
380 Cell.prototype.get_text = function () {
371 };
381 };
372
382
373 /**
383 /**
374 * should be overritten by subclass
384 * should be overritten by subclass
375 * @method set_text
385 * @method set_text
376 * @param {string} text
386 * @param {string} text
377 */
387 */
378 Cell.prototype.set_text = function (text) {
388 Cell.prototype.set_text = function (text) {
379 };
389 };
380
390
381 /**
391 /**
382 * should be overritten by subclass
392 * should be overritten by subclass
383 * serialise cell to json.
393 * serialise cell to json.
384 * @method toJSON
394 * @method toJSON
385 **/
395 **/
386 Cell.prototype.toJSON = function () {
396 Cell.prototype.toJSON = function () {
387 var data = {};
397 var data = {};
388 data.metadata = this.metadata;
398 data.metadata = this.metadata;
389 data.cell_type = this.cell_type;
399 data.cell_type = this.cell_type;
390 return data;
400 return data;
391 };
401 };
392
402
393
403
394 /**
404 /**
395 * should be overritten by subclass
405 * should be overritten by subclass
396 * @method fromJSON
406 * @method fromJSON
397 **/
407 **/
398 Cell.prototype.fromJSON = function (data) {
408 Cell.prototype.fromJSON = function (data) {
399 if (data.metadata !== undefined) {
409 if (data.metadata !== undefined) {
400 this.metadata = data.metadata;
410 this.metadata = data.metadata;
401 }
411 }
402 this.celltoolbar.rebuild();
412 this.celltoolbar.rebuild();
403 };
413 };
404
414
405
415
406 /**
416 /**
407 * can the cell be split into two cells
417 * can the cell be split into two cells
408 * @method is_splittable
418 * @method is_splittable
409 **/
419 **/
410 Cell.prototype.is_splittable = function () {
420 Cell.prototype.is_splittable = function () {
411 return true;
421 return true;
412 };
422 };
413
423
414
424
415 /**
425 /**
416 * can the cell be merged with other cells
426 * can the cell be merged with other cells
417 * @method is_mergeable
427 * @method is_mergeable
418 **/
428 **/
419 Cell.prototype.is_mergeable = function () {
429 Cell.prototype.is_mergeable = function () {
420 return true;
430 return true;
421 };
431 };
422
432
423
433
424 /**
434 /**
425 * @return {String} - the text before the cursor
435 * @return {String} - the text before the cursor
426 * @method get_pre_cursor
436 * @method get_pre_cursor
427 **/
437 **/
428 Cell.prototype.get_pre_cursor = function () {
438 Cell.prototype.get_pre_cursor = function () {
429 var cursor = this.code_mirror.getCursor();
439 var cursor = this.code_mirror.getCursor();
430 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
440 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
431 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
441 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
432 return text;
442 return text;
433 };
443 };
434
444
435
445
436 /**
446 /**
437 * @return {String} - the text after the cursor
447 * @return {String} - the text after the cursor
438 * @method get_post_cursor
448 * @method get_post_cursor
439 **/
449 **/
440 Cell.prototype.get_post_cursor = function () {
450 Cell.prototype.get_post_cursor = function () {
441 var cursor = this.code_mirror.getCursor();
451 var cursor = this.code_mirror.getCursor();
442 var last_line_num = this.code_mirror.lineCount()-1;
452 var last_line_num = this.code_mirror.lineCount()-1;
443 var last_line_len = this.code_mirror.getLine(last_line_num).length;
453 var last_line_len = this.code_mirror.getLine(last_line_num).length;
444 var end = {line:last_line_num, ch:last_line_len};
454 var end = {line:last_line_num, ch:last_line_len};
445 var text = this.code_mirror.getRange(cursor, end);
455 var text = this.code_mirror.getRange(cursor, end);
446 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
456 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
447 return text;
457 return text;
448 };
458 };
449
459
450 /**
460 /**
451 * Show/Hide CodeMirror LineNumber
461 * Show/Hide CodeMirror LineNumber
452 * @method show_line_numbers
462 * @method show_line_numbers
453 *
463 *
454 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
464 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
455 **/
465 **/
456 Cell.prototype.show_line_numbers = function (value) {
466 Cell.prototype.show_line_numbers = function (value) {
457 this.code_mirror.setOption('lineNumbers', value);
467 this.code_mirror.setOption('lineNumbers', value);
458 this.code_mirror.refresh();
468 this.code_mirror.refresh();
459 };
469 };
460
470
461 /**
471 /**
462 * Toggle CodeMirror LineNumber
472 * Toggle CodeMirror LineNumber
463 * @method toggle_line_numbers
473 * @method toggle_line_numbers
464 **/
474 **/
465 Cell.prototype.toggle_line_numbers = function () {
475 Cell.prototype.toggle_line_numbers = function () {
466 var val = this.code_mirror.getOption('lineNumbers');
476 var val = this.code_mirror.getOption('lineNumbers');
467 this.show_line_numbers(!val);
477 this.show_line_numbers(!val);
468 };
478 };
469
479
470 /**
480 /**
471 * Force codemirror highlight mode
481 * Force codemirror highlight mode
472 * @method force_highlight
482 * @method force_highlight
473 * @param {object} - CodeMirror mode
483 * @param {object} - CodeMirror mode
474 **/
484 **/
475 Cell.prototype.force_highlight = function(mode) {
485 Cell.prototype.force_highlight = function(mode) {
476 this.user_highlight = mode;
486 this.user_highlight = mode;
477 this.auto_highlight();
487 this.auto_highlight();
478 };
488 };
479
489
480 /**
490 /**
481 * Try to autodetect cell highlight mode, or use selected mode
491 * Try to autodetect cell highlight mode, or use selected mode
482 * @methods _auto_highlight
492 * @methods _auto_highlight
483 * @private
493 * @private
484 * @param {String|object|undefined} - CodeMirror mode | 'auto'
494 * @param {String|object|undefined} - CodeMirror mode | 'auto'
485 **/
495 **/
486 Cell.prototype._auto_highlight = function (modes) {
496 Cell.prototype._auto_highlight = function (modes) {
487 //Here we handle manually selected modes
497 //Here we handle manually selected modes
488 var mode;
498 var mode;
489 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
499 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
490 {
500 {
491 mode = this.user_highlight;
501 mode = this.user_highlight;
492 CodeMirror.autoLoadMode(this.code_mirror, mode);
502 CodeMirror.autoLoadMode(this.code_mirror, mode);
493 this.code_mirror.setOption('mode', mode);
503 this.code_mirror.setOption('mode', mode);
494 return;
504 return;
495 }
505 }
496 var current_mode = this.code_mirror.getOption('mode', mode);
506 var current_mode = this.code_mirror.getOption('mode', mode);
497 var first_line = this.code_mirror.getLine(0);
507 var first_line = this.code_mirror.getLine(0);
498 // loop on every pairs
508 // loop on every pairs
499 for(mode in modes) {
509 for(mode in modes) {
500 var regs = modes[mode].reg;
510 var regs = modes[mode].reg;
501 // only one key every time but regexp can't be keys...
511 // only one key every time but regexp can't be keys...
502 for(var i=0; i<regs.length; i++) {
512 for(var i=0; i<regs.length; i++) {
503 // here we handle non magic_modes
513 // here we handle non magic_modes
504 if(first_line.match(regs[i]) !== null) {
514 if(first_line.match(regs[i]) !== null) {
505 if(current_mode == mode){
515 if(current_mode == mode){
506 return;
516 return;
507 }
517 }
508 if (mode.search('magic_') !== 0) {
518 if (mode.search('magic_') !== 0) {
509 this.code_mirror.setOption('mode', mode);
519 this.code_mirror.setOption('mode', mode);
510 CodeMirror.autoLoadMode(this.code_mirror, mode);
520 CodeMirror.autoLoadMode(this.code_mirror, mode);
511 return;
521 return;
512 }
522 }
513 var open = modes[mode].open || "%%";
523 var open = modes[mode].open || "%%";
514 var close = modes[mode].close || "%%end";
524 var close = modes[mode].close || "%%end";
515 var mmode = mode;
525 var mmode = mode;
516 mode = mmode.substr(6);
526 mode = mmode.substr(6);
517 if(current_mode == mode){
527 if(current_mode == mode){
518 return;
528 return;
519 }
529 }
520 CodeMirror.autoLoadMode(this.code_mirror, mode);
530 CodeMirror.autoLoadMode(this.code_mirror, mode);
521 // create on the fly a mode that swhitch between
531 // create on the fly a mode that swhitch between
522 // plain/text and smth else otherwise `%%` is
532 // plain/text and smth else otherwise `%%` is
523 // source of some highlight issues.
533 // source of some highlight issues.
524 // we use patchedGetMode to circumvent a bug in CM
534 // we use patchedGetMode to circumvent a bug in CM
525 CodeMirror.defineMode(mmode , function(config) {
535 CodeMirror.defineMode(mmode , function(config) {
526 return CodeMirror.multiplexingMode(
536 return CodeMirror.multiplexingMode(
527 CodeMirror.patchedGetMode(config, 'text/plain'),
537 CodeMirror.patchedGetMode(config, 'text/plain'),
528 // always set someting on close
538 // always set someting on close
529 {open: open, close: close,
539 {open: open, close: close,
530 mode: CodeMirror.patchedGetMode(config, mode),
540 mode: CodeMirror.patchedGetMode(config, mode),
531 delimStyle: "delimit"
541 delimStyle: "delimit"
532 }
542 }
533 );
543 );
534 });
544 });
535 this.code_mirror.setOption('mode', mmode);
545 this.code_mirror.setOption('mode', mmode);
536 return;
546 return;
537 }
547 }
538 }
548 }
539 }
549 }
540 // fallback on default
550 // fallback on default
541 var default_mode;
551 var default_mode;
542 try {
552 try {
543 default_mode = this._options.cm_config.mode;
553 default_mode = this._options.cm_config.mode;
544 } catch(e) {
554 } catch(e) {
545 default_mode = 'text/plain';
555 default_mode = 'text/plain';
546 }
556 }
547 if( current_mode === default_mode){
557 if( current_mode === default_mode){
548 return;
558 return;
549 }
559 }
550 this.code_mirror.setOption('mode', default_mode);
560 this.code_mirror.setOption('mode', default_mode);
551 };
561 };
552
562
553 // Backwards compatibility.
563 // Backwards compatibility.
554 IPython.Cell = Cell;
564 IPython.Cell = Cell;
555
565
556 return {'Cell': Cell};
566 return {'Cell': Cell};
557 });
567 });
@@ -1,525 +1,521 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 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/keyboard',
8 'base/js/keyboard',
9 'notebook/js/cell',
9 'notebook/js/cell',
10 'notebook/js/outputarea',
10 'notebook/js/outputarea',
11 'notebook/js/completer',
11 'notebook/js/completer',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
13 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
14 "use strict";
14 "use strict";
15 var Cell = cell.Cell;
15 var Cell = cell.Cell;
16
16
17 /* local util for codemirror */
17 /* local util for codemirror */
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
19
19
20 /**
20 /**
21 *
21 *
22 * function to delete until previous non blanking space character
22 * function to delete until previous non blanking space character
23 * or first multiple of 4 tabstop.
23 * or first multiple of 4 tabstop.
24 * @private
24 * @private
25 */
25 */
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
30 var tabsize = cm.getOption('tabSize');
30 var tabsize = cm.getOption('tabSize');
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
33 var select = cm.getRange(from,cur);
33 var select = cm.getRange(from,cur);
34 if( select.match(/^\ +$/) !== null){
34 if( select.match(/^\ +$/) !== null){
35 cm.replaceRange("",from,cur);
35 cm.replaceRange("",from,cur);
36 } else {
36 } else {
37 cm.deleteH(-1,"char");
37 cm.deleteH(-1,"char");
38 }
38 }
39 };
39 };
40
40
41 var keycodes = keyboard.keycodes;
41 var keycodes = keyboard.keycodes;
42
42
43 var CodeCell = function (kernel, options) {
43 var CodeCell = function (kernel, options) {
44 // Constructor
44 // Constructor
45 //
45 //
46 // A Cell conceived to write code.
46 // A Cell conceived to write code.
47 //
47 //
48 // Parameters:
48 // Parameters:
49 // kernel: Kernel instance
49 // kernel: Kernel instance
50 // The kernel doesn't have to be set at creation time, in that case
50 // The kernel doesn't have to be set at creation time, in that case
51 // it will be null and set_kernel has to be called later.
51 // it will be null and set_kernel has to be called later.
52 // options: dictionary
52 // options: dictionary
53 // Dictionary of keyword arguments.
53 // Dictionary of keyword arguments.
54 // events: $(Events) instance
54 // events: $(Events) instance
55 // config: dictionary
55 // config: dictionary
56 // keyboard_manager: KeyboardManager instance
56 // keyboard_manager: KeyboardManager instance
57 // notebook: Notebook instance
57 // notebook: Notebook instance
58 // tooltip: Tooltip instance
58 // tooltip: Tooltip instance
59 this.kernel = kernel || null;
59 this.kernel = kernel || null;
60 this.notebook = options.notebook;
60 this.notebook = options.notebook;
61 this.collapsed = false;
61 this.collapsed = false;
62 this.events = options.events;
62 this.events = options.events;
63 this.tooltip = options.tooltip;
63 this.tooltip = options.tooltip;
64 this.config = options.config;
64 this.config = options.config;
65
65
66 // create all attributed in constructor function
66 // create all attributed in constructor function
67 // even if null for V8 VM optimisation
67 // even if null for V8 VM optimisation
68 this.input_prompt_number = null;
68 this.input_prompt_number = null;
69 this.celltoolbar = null;
69 this.celltoolbar = null;
70 this.output_area = null;
70 this.output_area = null;
71 this.last_msg_id = null;
71 this.last_msg_id = null;
72 this.completer = null;
72 this.completer = null;
73
73
74
74
75 var cm_overwrite_options = {
75 var cm_overwrite_options = {
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 };
77 };
78
78
79 var config = utils.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
79 var config = utils.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
80 Cell.apply(this,[{
80 Cell.apply(this,[{
81 config: config,
81 config: config,
82 keyboard_manager: options.keyboard_manager,
82 keyboard_manager: options.keyboard_manager,
83 events: this.events}]);
83 events: this.events}]);
84
84
85 // Attributes we want to override in this subclass.
85 // Attributes we want to override in this subclass.
86 this.cell_type = "code";
86 this.cell_type = "code";
87
87
88 var that = this;
88 var that = this;
89 this.element.focusout(
89 this.element.focusout(
90 function() { that.auto_highlight(); }
90 function() { that.auto_highlight(); }
91 );
91 );
92 };
92 };
93
93
94 CodeCell.options_default = {
94 CodeCell.options_default = {
95 cm_config : {
95 cm_config : {
96 extraKeys: {
96 extraKeys: {
97 "Tab" : "indentMore",
97 "Tab" : "indentMore",
98 "Shift-Tab" : "indentLess",
98 "Shift-Tab" : "indentLess",
99 "Backspace" : "delSpaceToPrevTabStop",
99 "Backspace" : "delSpaceToPrevTabStop",
100 "Cmd-/" : "toggleComment",
100 "Cmd-/" : "toggleComment",
101 "Ctrl-/" : "toggleComment"
101 "Ctrl-/" : "toggleComment"
102 },
102 },
103 mode: 'ipython',
103 mode: 'ipython',
104 theme: 'ipython',
104 theme: 'ipython',
105 matchBrackets: true,
105 matchBrackets: true,
106 // don't auto-close strings because of CodeMirror #2385
106 // don't auto-close strings because of CodeMirror #2385
107 autoCloseBrackets: "()[]{}"
107 autoCloseBrackets: "()[]{}"
108 }
108 }
109 };
109 };
110
110
111 CodeCell.msg_cells = {};
111 CodeCell.msg_cells = {};
112
112
113 CodeCell.prototype = new Cell();
113 CodeCell.prototype = new Cell();
114
114
115 /**
115 /**
116 * @method auto_highlight
116 * @method auto_highlight
117 */
117 */
118 CodeCell.prototype.auto_highlight = function () {
118 CodeCell.prototype.auto_highlight = function () {
119 this._auto_highlight(this.config.cell_magic_highlight);
119 this._auto_highlight(this.config.cell_magic_highlight);
120 };
120 };
121
121
122 /** @method create_element */
122 /** @method create_element */
123 CodeCell.prototype.create_element = function () {
123 CodeCell.prototype.create_element = function () {
124 Cell.prototype.create_element.apply(this, arguments);
124 Cell.prototype.create_element.apply(this, arguments);
125
125
126 var cell = $('<div></div>').addClass('cell code_cell');
126 var cell = $('<div></div>').addClass('cell code_cell');
127 cell.attr('tabindex','2');
127 cell.attr('tabindex','2');
128
128
129 var input = $('<div></div>').addClass('input');
129 var input = $('<div></div>').addClass('input');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
131 var inner_cell = $('<div/>').addClass('inner_cell');
131 var inner_cell = $('<div/>').addClass('inner_cell');
132 this.celltoolbar = new celltoolbar.CellToolbar({
132 this.celltoolbar = new celltoolbar.CellToolbar({
133 cell: this,
133 cell: this,
134 notebook: this.notebook});
134 notebook: this.notebook});
135 inner_cell.append(this.celltoolbar.element);
135 inner_cell.append(this.celltoolbar.element);
136 var input_area = $('<div/>').addClass('input_area');
136 var input_area = $('<div/>').addClass('input_area');
137 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
137 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
138 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
138 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
139 inner_cell.append(input_area);
139 inner_cell.append(input_area);
140 input.append(prompt).append(inner_cell);
140 input.append(prompt).append(inner_cell);
141
141
142 var widget_area = $('<div/>')
142 var widget_area = $('<div/>')
143 .addClass('widget-area')
143 .addClass('widget-area')
144 .hide();
144 .hide();
145 this.widget_area = widget_area;
145 this.widget_area = widget_area;
146 var widget_prompt = $('<div/>')
146 var widget_prompt = $('<div/>')
147 .addClass('prompt')
147 .addClass('prompt')
148 .appendTo(widget_area);
148 .appendTo(widget_area);
149 var widget_subarea = $('<div/>')
149 var widget_subarea = $('<div/>')
150 .addClass('widget-subarea')
150 .addClass('widget-subarea')
151 .appendTo(widget_area);
151 .appendTo(widget_area);
152 this.widget_subarea = widget_subarea;
152 this.widget_subarea = widget_subarea;
153 var widget_clear_buton = $('<button />')
153 var widget_clear_buton = $('<button />')
154 .addClass('close')
154 .addClass('close')
155 .html('&times;')
155 .html('&times;')
156 .click(function() {
156 .click(function() {
157 widget_area.slideUp('', function(){ widget_subarea.html(''); });
157 widget_area.slideUp('', function(){ widget_subarea.html(''); });
158 })
158 })
159 .appendTo(widget_prompt);
159 .appendTo(widget_prompt);
160
160
161 var output = $('<div></div>');
161 var output = $('<div></div>');
162 cell.append(input).append(widget_area).append(output);
162 cell.append(input).append(widget_area).append(output);
163 this.element = cell;
163 this.element = cell;
164 this.output_area = new outputarea.OutputArea({
164 this.output_area = new outputarea.OutputArea({
165 selector: output,
165 selector: output,
166 prompt_area: true,
166 prompt_area: true,
167 events: this.events,
167 events: this.events,
168 keyboard_manager: this.keyboard_manager});
168 keyboard_manager: this.keyboard_manager});
169 this.completer = new completer.Completer(this, this.events);
169 this.completer = new completer.Completer(this, this.events);
170 };
170 };
171
171
172 /** @method bind_events */
172 /** @method bind_events */
173 CodeCell.prototype.bind_events = function () {
173 CodeCell.prototype.bind_events = function () {
174 Cell.prototype.bind_events.apply(this);
174 Cell.prototype.bind_events.apply(this);
175 var that = this;
175 var that = this;
176
176
177 this.element.focusout(
177 this.element.focusout(
178 function() { that.auto_highlight(); }
178 function() { that.auto_highlight(); }
179 );
179 );
180 };
180 };
181
181
182
182
183 /**
183 /**
184 * This method gets called in CodeMirror's onKeyDown/onKeyPress
184 * This method gets called in CodeMirror's onKeyDown/onKeyPress
185 * handlers and is used to provide custom key handling. Its return
185 * handlers and is used to provide custom key handling. Its return
186 * value is used to determine if CodeMirror should ignore the event:
186 * value is used to determine if CodeMirror should ignore the event:
187 * true = ignore, false = don't ignore.
187 * true = ignore, false = don't ignore.
188 * @method handle_codemirror_keyevent
188 * @method handle_codemirror_keyevent
189 */
189 */
190 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
190 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
191
191
192 var that = this;
192 var that = this;
193 // whatever key is pressed, first, cancel the tooltip request before
193 // whatever key is pressed, first, cancel the tooltip request before
194 // they are sent, and remove tooltip if any, except for tab again
194 // they are sent, and remove tooltip if any, except for tab again
195 var tooltip_closed = null;
195 var tooltip_closed = null;
196 if (event.type === 'keydown' && event.which != keycodes.tab ) {
196 if (event.type === 'keydown' && event.which != keycodes.tab ) {
197 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
197 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
198 }
198 }
199
199
200 var cur = editor.getCursor();
200 var cur = editor.getCursor();
201 if (event.keyCode === keycodes.enter){
201 if (event.keyCode === keycodes.enter){
202 this.auto_highlight();
202 this.auto_highlight();
203 }
203 }
204
204
205 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
205 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
206 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
206 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
207 // browser and keyboard layout !
207 // browser and keyboard layout !
208 // Pressing '(' , request tooltip, don't forget to reappend it
208 // Pressing '(' , request tooltip, don't forget to reappend it
209 // The second argument says to hide the tooltip if the docstring
209 // The second argument says to hide the tooltip if the docstring
210 // is actually empty
210 // is actually empty
211 this.tooltip.pending(that, true);
211 this.tooltip.pending(that, true);
212 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
212 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
213 // If tooltip is active, cancel it. The call to
213 // If tooltip is active, cancel it. The call to
214 // remove_and_cancel_tooltip above doesn't pass, force=true.
214 // remove_and_cancel_tooltip above doesn't pass, force=true.
215 // Because of this it won't actually close the tooltip
215 // Because of this it won't actually close the tooltip
216 // if it is in sticky mode. Thus, we have to check again if it is open
216 // if it is in sticky mode. Thus, we have to check again if it is open
217 // and close it with force=true.
217 // and close it with force=true.
218 if (!this.tooltip._hidden) {
218 if (!this.tooltip._hidden) {
219 this.tooltip.remove_and_cancel_tooltip(true);
219 this.tooltip.remove_and_cancel_tooltip(true);
220 }
220 }
221 // If we closed the tooltip, don't let CM or the global handlers
221 // If we closed the tooltip, don't let CM or the global handlers
222 // handle this event.
222 // handle this event.
223 event.stop();
223 event.stop();
224 return true;
224 return true;
225 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
225 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
226 if (editor.somethingSelected()){
226 if (editor.somethingSelected()){
227 var anchor = editor.getCursor("anchor");
227 var anchor = editor.getCursor("anchor");
228 var head = editor.getCursor("head");
228 var head = editor.getCursor("head");
229 if( anchor.line != head.line){
229 if( anchor.line != head.line){
230 return false;
230 return false;
231 }
231 }
232 }
232 }
233 this.tooltip.request(that);
233 this.tooltip.request(that);
234 event.stop();
234 event.stop();
235 return true;
235 return true;
236 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
236 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
237 // Tab completion.
237 // Tab completion.
238 this.tooltip.remove_and_cancel_tooltip();
238 this.tooltip.remove_and_cancel_tooltip();
239 if (editor.somethingSelected()) {
239 if (editor.somethingSelected()) {
240 return false;
240 return false;
241 }
241 }
242 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
242 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
243 if (pre_cursor.trim() === "") {
243 if (pre_cursor.trim() === "") {
244 // Don't autocomplete if the part of the line before the cursor
244 // Don't autocomplete if the part of the line before the cursor
245 // is empty. In this case, let CodeMirror handle indentation.
245 // is empty. In this case, let CodeMirror handle indentation.
246 return false;
246 return false;
247 } else {
247 } else {
248 event.stop();
248 event.stop();
249 this.completer.startCompletion();
249 this.completer.startCompletion();
250 return true;
250 return true;
251 }
251 }
252 }
252 }
253
253
254 // keyboard event wasn't one of those unique to code cells, let's see
254 // keyboard event wasn't one of those unique to code cells, let's see
255 // if it's one of the generic ones (i.e. check edit mode shortcuts)
255 // if it's one of the generic ones (i.e. check edit mode shortcuts)
256 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
256 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
257 };
257 };
258
258
259 // Kernel related calls.
259 // Kernel related calls.
260
260
261 CodeCell.prototype.set_kernel = function (kernel) {
261 CodeCell.prototype.set_kernel = function (kernel) {
262 this.kernel = kernel;
262 this.kernel = kernel;
263 };
263 };
264
264
265 /**
265 /**
266 * Execute current code cell to the kernel
266 * Execute current code cell to the kernel
267 * @method execute
267 * @method execute
268 */
268 */
269 CodeCell.prototype.execute = function () {
269 CodeCell.prototype.execute = function () {
270 this.output_area.clear_output();
270 this.output_area.clear_output();
271
271
272 // Clear widget area
272 // Clear widget area
273 this.widget_subarea.html('');
273 this.widget_subarea.html('');
274 this.widget_subarea.height('');
274 this.widget_subarea.height('');
275 this.widget_area.height('');
275 this.widget_area.height('');
276 this.widget_area.hide();
276 this.widget_area.hide();
277
277
278 this.set_input_prompt('*');
278 this.set_input_prompt('*');
279 this.element.addClass("running");
279 this.element.addClass("running");
280 if (this.last_msg_id) {
280 if (this.last_msg_id) {
281 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
281 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
282 }
282 }
283 var callbacks = this.get_callbacks();
283 var callbacks = this.get_callbacks();
284
284
285 var old_msg_id = this.last_msg_id;
285 var old_msg_id = this.last_msg_id;
286 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
286 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
287 if (old_msg_id) {
287 if (old_msg_id) {
288 delete CodeCell.msg_cells[old_msg_id];
288 delete CodeCell.msg_cells[old_msg_id];
289 }
289 }
290 CodeCell.msg_cells[this.last_msg_id] = this;
290 CodeCell.msg_cells[this.last_msg_id] = this;
291 this.render();
291 };
292 };
292
293
293 /**
294 /**
294 * Construct the default callbacks for
295 * Construct the default callbacks for
295 * @method get_callbacks
296 * @method get_callbacks
296 */
297 */
297 CodeCell.prototype.get_callbacks = function () {
298 CodeCell.prototype.get_callbacks = function () {
298 return {
299 return {
299 shell : {
300 shell : {
300 reply : $.proxy(this._handle_execute_reply, this),
301 reply : $.proxy(this._handle_execute_reply, this),
301 payload : {
302 payload : {
302 set_next_input : $.proxy(this._handle_set_next_input, this),
303 set_next_input : $.proxy(this._handle_set_next_input, this),
303 page : $.proxy(this._open_with_pager, this)
304 page : $.proxy(this._open_with_pager, this)
304 }
305 }
305 },
306 },
306 iopub : {
307 iopub : {
307 output : $.proxy(this.output_area.handle_output, this.output_area),
308 output : $.proxy(this.output_area.handle_output, this.output_area),
308 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
309 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
309 },
310 },
310 input : $.proxy(this._handle_input_request, this)
311 input : $.proxy(this._handle_input_request, this)
311 };
312 };
312 };
313 };
313
314
314 CodeCell.prototype._open_with_pager = function (payload) {
315 CodeCell.prototype._open_with_pager = function (payload) {
315 this.events.trigger('open_with_text.Pager', payload);
316 this.events.trigger('open_with_text.Pager', payload);
316 };
317 };
317
318
318 /**
319 /**
319 * @method _handle_execute_reply
320 * @method _handle_execute_reply
320 * @private
321 * @private
321 */
322 */
322 CodeCell.prototype._handle_execute_reply = function (msg) {
323 CodeCell.prototype._handle_execute_reply = function (msg) {
323 this.set_input_prompt(msg.content.execution_count);
324 this.set_input_prompt(msg.content.execution_count);
324 this.element.removeClass("running");
325 this.element.removeClass("running");
325 this.events.trigger('set_dirty.Notebook', {value: true});
326 this.events.trigger('set_dirty.Notebook', {value: true});
326 };
327 };
327
328
328 /**
329 /**
329 * @method _handle_set_next_input
330 * @method _handle_set_next_input
330 * @private
331 * @private
331 */
332 */
332 CodeCell.prototype._handle_set_next_input = function (payload) {
333 CodeCell.prototype._handle_set_next_input = function (payload) {
333 var data = {'cell': this, 'text': payload.text};
334 var data = {'cell': this, 'text': payload.text};
334 this.events.trigger('set_next_input.Notebook', data);
335 this.events.trigger('set_next_input.Notebook', data);
335 };
336 };
336
337
337 /**
338 /**
338 * @method _handle_input_request
339 * @method _handle_input_request
339 * @private
340 * @private
340 */
341 */
341 CodeCell.prototype._handle_input_request = function (msg) {
342 CodeCell.prototype._handle_input_request = function (msg) {
342 this.output_area.append_raw_input(msg);
343 this.output_area.append_raw_input(msg);
343 };
344 };
344
345
345
346
346 // Basic cell manipulation.
347 // Basic cell manipulation.
347
348
348 CodeCell.prototype.select = function () {
349 CodeCell.prototype.select = function () {
349 var cont = Cell.prototype.select.apply(this);
350 var cont = Cell.prototype.select.apply(this);
350 if (cont) {
351 if (cont) {
351 this.code_mirror.refresh();
352 this.code_mirror.refresh();
352 this.auto_highlight();
353 this.auto_highlight();
353 }
354 }
354 return cont;
355 return cont;
355 };
356 };
356
357
357 CodeCell.prototype.render = function () {
358 CodeCell.prototype.render = function () {
358 var cont = Cell.prototype.render.apply(this);
359 var cont = Cell.prototype.render.apply(this);
359 // Always execute, even if we are already in the rendered state
360 // Always execute, even if we are already in the rendered state
360 return cont;
361 return cont;
361 };
362 };
362
363
363 CodeCell.prototype.unrender = function () {
364 // CodeCell is always rendered
365 return false;
366 };
367
368 CodeCell.prototype.select_all = function () {
364 CodeCell.prototype.select_all = function () {
369 var start = {line: 0, ch: 0};
365 var start = {line: 0, ch: 0};
370 var nlines = this.code_mirror.lineCount();
366 var nlines = this.code_mirror.lineCount();
371 var last_line = this.code_mirror.getLine(nlines-1);
367 var last_line = this.code_mirror.getLine(nlines-1);
372 var end = {line: nlines-1, ch: last_line.length};
368 var end = {line: nlines-1, ch: last_line.length};
373 this.code_mirror.setSelection(start, end);
369 this.code_mirror.setSelection(start, end);
374 };
370 };
375
371
376
372
377 CodeCell.prototype.collapse_output = function () {
373 CodeCell.prototype.collapse_output = function () {
378 this.collapsed = true;
374 this.collapsed = true;
379 this.output_area.collapse();
375 this.output_area.collapse();
380 };
376 };
381
377
382
378
383 CodeCell.prototype.expand_output = function () {
379 CodeCell.prototype.expand_output = function () {
384 this.collapsed = false;
380 this.collapsed = false;
385 this.output_area.expand();
381 this.output_area.expand();
386 this.output_area.unscroll_area();
382 this.output_area.unscroll_area();
387 };
383 };
388
384
389 CodeCell.prototype.scroll_output = function () {
385 CodeCell.prototype.scroll_output = function () {
390 this.output_area.expand();
386 this.output_area.expand();
391 this.output_area.scroll_if_long();
387 this.output_area.scroll_if_long();
392 };
388 };
393
389
394 CodeCell.prototype.toggle_output = function () {
390 CodeCell.prototype.toggle_output = function () {
395 this.collapsed = Boolean(1 - this.collapsed);
391 this.collapsed = Boolean(1 - this.collapsed);
396 this.output_area.toggle_output();
392 this.output_area.toggle_output();
397 };
393 };
398
394
399 CodeCell.prototype.toggle_output_scroll = function () {
395 CodeCell.prototype.toggle_output_scroll = function () {
400 this.output_area.toggle_scroll();
396 this.output_area.toggle_scroll();
401 };
397 };
402
398
403
399
404 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
400 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
405 var ns;
401 var ns;
406 if (prompt_value === undefined) {
402 if (prompt_value === undefined) {
407 ns = "&nbsp;";
403 ns = "&nbsp;";
408 } else {
404 } else {
409 ns = encodeURIComponent(prompt_value);
405 ns = encodeURIComponent(prompt_value);
410 }
406 }
411 return 'In&nbsp;[' + ns + ']:';
407 return 'In&nbsp;[' + ns + ']:';
412 };
408 };
413
409
414 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
410 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
415 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
411 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
416 for(var i=1; i < lines_number; i++) {
412 for(var i=1; i < lines_number; i++) {
417 html.push(['...:']);
413 html.push(['...:']);
418 }
414 }
419 return html.join('<br/>');
415 return html.join('<br/>');
420 };
416 };
421
417
422 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
418 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
423
419
424
420
425 CodeCell.prototype.set_input_prompt = function (number) {
421 CodeCell.prototype.set_input_prompt = function (number) {
426 var nline = 1;
422 var nline = 1;
427 if (this.code_mirror !== undefined) {
423 if (this.code_mirror !== undefined) {
428 nline = this.code_mirror.lineCount();
424 nline = this.code_mirror.lineCount();
429 }
425 }
430 this.input_prompt_number = number;
426 this.input_prompt_number = number;
431 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
427 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
432 // This HTML call is okay because the user contents are escaped.
428 // This HTML call is okay because the user contents are escaped.
433 this.element.find('div.input_prompt').html(prompt_html);
429 this.element.find('div.input_prompt').html(prompt_html);
434 };
430 };
435
431
436
432
437 CodeCell.prototype.clear_input = function () {
433 CodeCell.prototype.clear_input = function () {
438 this.code_mirror.setValue('');
434 this.code_mirror.setValue('');
439 };
435 };
440
436
441
437
442 CodeCell.prototype.get_text = function () {
438 CodeCell.prototype.get_text = function () {
443 return this.code_mirror.getValue();
439 return this.code_mirror.getValue();
444 };
440 };
445
441
446
442
447 CodeCell.prototype.set_text = function (code) {
443 CodeCell.prototype.set_text = function (code) {
448 return this.code_mirror.setValue(code);
444 return this.code_mirror.setValue(code);
449 };
445 };
450
446
451
447
452 CodeCell.prototype.clear_output = function (wait) {
448 CodeCell.prototype.clear_output = function (wait) {
453 this.output_area.clear_output(wait);
449 this.output_area.clear_output(wait);
454 this.set_input_prompt();
450 this.set_input_prompt();
455 };
451 };
456
452
457
453
458 // JSON serialization
454 // JSON serialization
459
455
460 CodeCell.prototype.fromJSON = function (data) {
456 CodeCell.prototype.fromJSON = function (data) {
461 Cell.prototype.fromJSON.apply(this, arguments);
457 Cell.prototype.fromJSON.apply(this, arguments);
462 if (data.cell_type === 'code') {
458 if (data.cell_type === 'code') {
463 if (data.input !== undefined) {
459 if (data.input !== undefined) {
464 this.set_text(data.input);
460 this.set_text(data.input);
465 // make this value the starting point, so that we can only undo
461 // make this value the starting point, so that we can only undo
466 // to this state, instead of a blank cell
462 // to this state, instead of a blank cell
467 this.code_mirror.clearHistory();
463 this.code_mirror.clearHistory();
468 this.auto_highlight();
464 this.auto_highlight();
469 }
465 }
470 if (data.prompt_number !== undefined) {
466 if (data.prompt_number !== undefined) {
471 this.set_input_prompt(data.prompt_number);
467 this.set_input_prompt(data.prompt_number);
472 } else {
468 } else {
473 this.set_input_prompt();
469 this.set_input_prompt();
474 }
470 }
475 this.output_area.trusted = data.trusted || false;
471 this.output_area.trusted = data.trusted || false;
476 this.output_area.fromJSON(data.outputs);
472 this.output_area.fromJSON(data.outputs);
477 if (data.collapsed !== undefined) {
473 if (data.collapsed !== undefined) {
478 if (data.collapsed) {
474 if (data.collapsed) {
479 this.collapse_output();
475 this.collapse_output();
480 } else {
476 } else {
481 this.expand_output();
477 this.expand_output();
482 }
478 }
483 }
479 }
484 }
480 }
485 };
481 };
486
482
487
483
488 CodeCell.prototype.toJSON = function () {
484 CodeCell.prototype.toJSON = function () {
489 var data = Cell.prototype.toJSON.apply(this);
485 var data = Cell.prototype.toJSON.apply(this);
490 data.input = this.get_text();
486 data.input = this.get_text();
491 // is finite protect against undefined and '*' value
487 // is finite protect against undefined and '*' value
492 if (isFinite(this.input_prompt_number)) {
488 if (isFinite(this.input_prompt_number)) {
493 data.prompt_number = this.input_prompt_number;
489 data.prompt_number = this.input_prompt_number;
494 }
490 }
495 var outputs = this.output_area.toJSON();
491 var outputs = this.output_area.toJSON();
496 data.outputs = outputs;
492 data.outputs = outputs;
497 data.language = 'python';
493 data.language = 'python';
498 data.trusted = this.output_area.trusted;
494 data.trusted = this.output_area.trusted;
499 data.collapsed = this.collapsed;
495 data.collapsed = this.collapsed;
500 return data;
496 return data;
501 };
497 };
502
498
503 /**
499 /**
504 * handle cell level logic when a cell is unselected
500 * handle cell level logic when a cell is unselected
505 * @method unselect
501 * @method unselect
506 * @return is the action being taken
502 * @return is the action being taken
507 */
503 */
508 CodeCell.prototype.unselect = function () {
504 CodeCell.prototype.unselect = function () {
509 var cont = Cell.prototype.unselect.apply(this);
505 var cont = Cell.prototype.unselect.apply(this);
510 if (cont) {
506 if (cont) {
511 // When a code cell is usnelected, make sure that the corresponding
507 // When a code cell is usnelected, make sure that the corresponding
512 // tooltip and completer to that cell is closed.
508 // tooltip and completer to that cell is closed.
513 this.tooltip.remove_and_cancel_tooltip(true);
509 this.tooltip.remove_and_cancel_tooltip(true);
514 if (this.completer !== null) {
510 if (this.completer !== null) {
515 this.completer.close();
511 this.completer.close();
516 }
512 }
517 }
513 }
518 return cont;
514 return cont;
519 };
515 };
520
516
521 // Backwards compatability.
517 // Backwards compatability.
522 IPython.CodeCell = CodeCell;
518 IPython.CodeCell = CodeCell;
523
519
524 return {'CodeCell': CodeCell};
520 return {'CodeCell': CodeCell};
525 });
521 });
@@ -1,442 +1,421 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 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 'notebook/js/celltoolbar',
11 'notebook/js/celltoolbar',
12 'components/marked/lib/marked',
12 'components/marked/lib/marked',
13 ], function(IPython, utils, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 ], function(IPython, utils, $, cell, security, mathjaxutils, celltoolbar, marked) {
14 "use strict";
14 "use strict";
15 var Cell = cell.Cell;
15 var Cell = cell.Cell;
16
16
17 var TextCell = function (options) {
17 var TextCell = function (options) {
18 // Constructor
18 // Constructor
19 //
19 //
20 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
21 // and cell type is 'text' cell start as not redered.
21 // and cell type is 'text' cell start as not redered.
22 //
22 //
23 // Parameters:
23 // Parameters:
24 // options: dictionary
24 // options: dictionary
25 // Dictionary of keyword arguments.
25 // Dictionary of keyword arguments.
26 // events: $(Events) instance
26 // events: $(Events) instance
27 // config: dictionary
27 // config: dictionary
28 // keyboard_manager: KeyboardManager instance
28 // keyboard_manager: KeyboardManager instance
29 // notebook: Notebook instance
29 // notebook: Notebook instance
30 options = options || {};
30 options = options || {};
31
31
32 // in all TextCell/Cell subclasses
32 // in all TextCell/Cell subclasses
33 // do not assign most of members here, just pass it down
33 // do not assign most of members here, just pass it down
34 // in the options dict potentially overwriting what you wish.
34 // in the options dict potentially overwriting what you wish.
35 // they will be assigned in the base class.
35 // they will be assigned in the base class.
36 this.notebook = options.notebook;
36 this.notebook = options.notebook;
37 this.events = options.events;
37 this.events = options.events;
38 this.config = options.config;
38 this.config = options.config;
39
39
40 // we cannot put this as a class key as it has handle to "this".
40 // we cannot put this as a class key as it has handle to "this".
41 var cm_overwrite_options = {
41 var cm_overwrite_options = {
42 onKeyEvent: $.proxy(this.handle_keyevent,this)
42 onKeyEvent: $.proxy(this.handle_keyevent,this)
43 };
43 };
44 var config = utils.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
44 var config = utils.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
45 Cell.apply(this, [{
45 Cell.apply(this, [{
46 config: config,
46 config: config,
47 keyboard_manager: options.keyboard_manager,
47 keyboard_manager: options.keyboard_manager,
48 events: this.events}]);
48 events: this.events}]);
49
49
50 this.cell_type = this.cell_type || 'text';
50 this.cell_type = this.cell_type || 'text';
51 mathjaxutils = mathjaxutils;
51 mathjaxutils = mathjaxutils;
52 this.rendered = false;
52 this.rendered = false;
53 };
53 };
54
54
55 TextCell.prototype = new Cell();
55 TextCell.prototype = new Cell();
56
56
57 TextCell.options_default = {
57 TextCell.options_default = {
58 cm_config : {
58 cm_config : {
59 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
59 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 mode: 'htmlmixed',
60 mode: 'htmlmixed',
61 lineWrapping : true,
61 lineWrapping : true,
62 }
62 }
63 };
63 };
64
64
65
65
66 /**
66 /**
67 * Create the DOM element of the TextCell
67 * Create the DOM element of the TextCell
68 * @method create_element
68 * @method create_element
69 * @private
69 * @private
70 */
70 */
71 TextCell.prototype.create_element = function () {
71 TextCell.prototype.create_element = function () {
72 Cell.prototype.create_element.apply(this, arguments);
72 Cell.prototype.create_element.apply(this, arguments);
73
73
74 var cell = $("<div>").addClass('cell text_cell');
74 var cell = $("<div>").addClass('cell text_cell');
75 cell.attr('tabindex','2');
75 cell.attr('tabindex','2');
76
76
77 var prompt = $('<div/>').addClass('prompt input_prompt');
77 var prompt = $('<div/>').addClass('prompt input_prompt');
78 cell.append(prompt);
78 cell.append(prompt);
79 var inner_cell = $('<div/>').addClass('inner_cell');
79 var inner_cell = $('<div/>').addClass('inner_cell');
80 this.celltoolbar = new celltoolbar.CellToolbar({
80 this.celltoolbar = new celltoolbar.CellToolbar({
81 cell: this,
81 cell: this,
82 notebook: this.notebook});
82 notebook: this.notebook});
83 inner_cell.append(this.celltoolbar.element);
83 inner_cell.append(this.celltoolbar.element);
84 var input_area = $('<div/>').addClass('input_area');
84 var input_area = $('<div/>').addClass('input_area');
85 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
85 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
86 // The tabindex=-1 makes this div focusable.
86 // The tabindex=-1 makes this div focusable.
87 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
87 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
88 .attr('tabindex','-1');
88 .attr('tabindex','-1');
89 inner_cell.append(input_area).append(render_area);
89 inner_cell.append(input_area).append(render_area);
90 cell.append(inner_cell);
90 cell.append(inner_cell);
91 this.element = cell;
91 this.element = cell;
92 };
92 };
93
93
94
94
95 /**
96 * Bind the DOM evet to cell actions
97 * Need to be called after TextCell.create_element
98 * @private
99 * @method bind_event
100 */
101 TextCell.prototype.bind_events = function () {
102 Cell.prototype.bind_events.apply(this);
103 var that = this;
104
105 this.element.dblclick(function () {
106 if (that.selected === false) {
107 this.events.trigger('select.Cell', {'cell':that});
108 }
109 var cont = that.unrender();
110 if (cont) {
111 that.focus_editor();
112 }
113 });
114 };
115
116 // Cell level actions
95 // Cell level actions
117
96
118 TextCell.prototype.select = function () {
97 TextCell.prototype.select = function () {
119 var cont = Cell.prototype.select.apply(this);
98 var cont = Cell.prototype.select.apply(this);
120 if (cont) {
99 if (cont) {
121 if (this.mode === 'edit') {
100 if (this.mode === 'edit') {
122 this.code_mirror.refresh();
101 this.code_mirror.refresh();
123 }
102 }
124 }
103 }
125 return cont;
104 return cont;
126 };
105 };
127
106
128 TextCell.prototype.unrender = function () {
107 TextCell.prototype.unrender = function () {
129 if (this.read_only) return;
108 if (this.read_only) return;
130 var cont = Cell.prototype.unrender.apply(this);
109 var cont = Cell.prototype.unrender.apply(this);
131 if (cont) {
110 if (cont) {
132 var text_cell = this.element;
111 var text_cell = this.element;
133 var output = text_cell.find("div.text_cell_render");
112 var output = text_cell.find("div.text_cell_render");
134 if (this.get_text() === this.placeholder) {
113 if (this.get_text() === this.placeholder) {
135 this.set_text('');
114 this.set_text('');
136 }
115 }
137 this.refresh();
116 this.refresh();
138 }
117 }
139 return cont;
118 return cont;
140 };
119 };
141
120
142 TextCell.prototype.execute = function () {
121 TextCell.prototype.execute = function () {
143 this.render();
122 this.render();
144 };
123 };
145
124
146 /**
125 /**
147 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
126 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
148 * @method get_text
127 * @method get_text
149 * @retrun {string} CodeMirror current text value
128 * @retrun {string} CodeMirror current text value
150 */
129 */
151 TextCell.prototype.get_text = function() {
130 TextCell.prototype.get_text = function() {
152 return this.code_mirror.getValue();
131 return this.code_mirror.getValue();
153 };
132 };
154
133
155 /**
134 /**
156 * @param {string} text - Codemiror text value
135 * @param {string} text - Codemiror text value
157 * @see TextCell#get_text
136 * @see TextCell#get_text
158 * @method set_text
137 * @method set_text
159 * */
138 * */
160 TextCell.prototype.set_text = function(text) {
139 TextCell.prototype.set_text = function(text) {
161 this.code_mirror.setValue(text);
140 this.code_mirror.setValue(text);
162 this.unrender();
141 this.unrender();
163 this.code_mirror.refresh();
142 this.code_mirror.refresh();
164 };
143 };
165
144
166 /**
145 /**
167 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
146 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
168 * @method get_rendered
147 * @method get_rendered
169 * */
148 * */
170 TextCell.prototype.get_rendered = function() {
149 TextCell.prototype.get_rendered = function() {
171 return this.element.find('div.text_cell_render').html();
150 return this.element.find('div.text_cell_render').html();
172 };
151 };
173
152
174 /**
153 /**
175 * @method set_rendered
154 * @method set_rendered
176 */
155 */
177 TextCell.prototype.set_rendered = function(text) {
156 TextCell.prototype.set_rendered = function(text) {
178 this.element.find('div.text_cell_render').html(text);
157 this.element.find('div.text_cell_render').html(text);
179 };
158 };
180
159
181
160
182 /**
161 /**
183 * Create Text cell from JSON
162 * Create Text cell from JSON
184 * @param {json} data - JSON serialized text-cell
163 * @param {json} data - JSON serialized text-cell
185 * @method fromJSON
164 * @method fromJSON
186 */
165 */
187 TextCell.prototype.fromJSON = function (data) {
166 TextCell.prototype.fromJSON = function (data) {
188 Cell.prototype.fromJSON.apply(this, arguments);
167 Cell.prototype.fromJSON.apply(this, arguments);
189 if (data.cell_type === this.cell_type) {
168 if (data.cell_type === this.cell_type) {
190 if (data.source !== undefined) {
169 if (data.source !== undefined) {
191 this.set_text(data.source);
170 this.set_text(data.source);
192 // make this value the starting point, so that we can only undo
171 // make this value the starting point, so that we can only undo
193 // to this state, instead of a blank cell
172 // to this state, instead of a blank cell
194 this.code_mirror.clearHistory();
173 this.code_mirror.clearHistory();
195 // TODO: This HTML needs to be treated as potentially dangerous
174 // TODO: This HTML needs to be treated as potentially dangerous
196 // user input and should be handled before set_rendered.
175 // user input and should be handled before set_rendered.
197 this.set_rendered(data.rendered || '');
176 this.set_rendered(data.rendered || '');
198 this.rendered = false;
177 this.rendered = false;
199 this.render();
178 this.render();
200 }
179 }
201 }
180 }
202 };
181 };
203
182
204 /** Generate JSON from cell
183 /** Generate JSON from cell
205 * @return {object} cell data serialised to json
184 * @return {object} cell data serialised to json
206 */
185 */
207 TextCell.prototype.toJSON = function () {
186 TextCell.prototype.toJSON = function () {
208 var data = Cell.prototype.toJSON.apply(this);
187 var data = Cell.prototype.toJSON.apply(this);
209 data.source = this.get_text();
188 data.source = this.get_text();
210 if (data.source == this.placeholder) {
189 if (data.source == this.placeholder) {
211 data.source = "";
190 data.source = "";
212 }
191 }
213 return data;
192 return data;
214 };
193 };
215
194
216
195
217 var MarkdownCell = function (options) {
196 var MarkdownCell = function (options) {
218 // Constructor
197 // Constructor
219 //
198 //
220 // Parameters:
199 // Parameters:
221 // options: dictionary
200 // options: dictionary
222 // Dictionary of keyword arguments.
201 // Dictionary of keyword arguments.
223 // events: $(Events) instance
202 // events: $(Events) instance
224 // config: dictionary
203 // config: dictionary
225 // keyboard_manager: KeyboardManager instance
204 // keyboard_manager: KeyboardManager instance
226 // notebook: Notebook instance
205 // notebook: Notebook instance
227 options = options || {};
206 options = options || {};
228 var config = utils.mergeopt(MarkdownCell, options.config);
207 var config = utils.mergeopt(MarkdownCell, options.config);
229 TextCell.apply(this, [$.extend({}, options, {config: config})]);
208 TextCell.apply(this, [$.extend({}, options, {config: config})]);
230
209
231 this.cell_type = 'markdown';
210 this.cell_type = 'markdown';
232 };
211 };
233
212
234 MarkdownCell.options_default = {
213 MarkdownCell.options_default = {
235 cm_config: {
214 cm_config: {
236 mode: 'ipythongfm'
215 mode: 'ipythongfm'
237 },
216 },
238 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
217 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
239 };
218 };
240
219
241 MarkdownCell.prototype = new TextCell();
220 MarkdownCell.prototype = new TextCell();
242
221
243 /**
222 /**
244 * @method render
223 * @method render
245 */
224 */
246 MarkdownCell.prototype.render = function () {
225 MarkdownCell.prototype.render = function () {
247 var cont = TextCell.prototype.render.apply(this);
226 var cont = TextCell.prototype.render.apply(this);
248 if (cont) {
227 if (cont) {
249 var text = this.get_text();
228 var text = this.get_text();
250 var math = null;
229 var math = null;
251 if (text === "") { text = this.placeholder; }
230 if (text === "") { text = this.placeholder; }
252 var text_and_math = mathjaxutils.remove_math(text);
231 var text_and_math = mathjaxutils.remove_math(text);
253 text = text_and_math[0];
232 text = text_and_math[0];
254 math = text_and_math[1];
233 math = text_and_math[1];
255 var html = marked.parser(marked.lexer(text));
234 var html = marked.parser(marked.lexer(text));
256 html = mathjaxutils.replace_math(html, math);
235 html = mathjaxutils.replace_math(html, math);
257 html = security.sanitize_html(html);
236 html = security.sanitize_html(html);
258 html = $($.parseHTML(html));
237 html = $($.parseHTML(html));
259 // links in markdown cells should open in new tabs
238 // links in markdown cells should open in new tabs
260 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
239 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
261 this.set_rendered(html);
240 this.set_rendered(html);
262 this.typeset();
241 this.typeset();
263 }
242 }
264 return cont;
243 return cont;
265 };
244 };
266
245
267
246
268 var RawCell = function (options) {
247 var RawCell = function (options) {
269 // Constructor
248 // Constructor
270 //
249 //
271 // Parameters:
250 // Parameters:
272 // options: dictionary
251 // options: dictionary
273 // Dictionary of keyword arguments.
252 // Dictionary of keyword arguments.
274 // events: $(Events) instance
253 // events: $(Events) instance
275 // config: dictionary
254 // config: dictionary
276 // keyboard_manager: KeyboardManager instance
255 // keyboard_manager: KeyboardManager instance
277 // notebook: Notebook instance
256 // notebook: Notebook instance
278 options = options || {};
257 options = options || {};
279 var config = utils.mergeopt(RawCell, options.config);
258 var config = utils.mergeopt(RawCell, options.config);
280 TextCell.apply(this, [$.extend({}, options, {config: config})]);
259 TextCell.apply(this, [$.extend({}, options, {config: config})]);
281
260
282 this.cell_type = 'raw';
261 this.cell_type = 'raw';
283 };
262 };
284
263
285 RawCell.options_default = {
264 RawCell.options_default = {
286 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
265 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
287 "It will not be rendered in the notebook. " +
266 "It will not be rendered in the notebook. " +
288 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
267 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
289 };
268 };
290
269
291 RawCell.prototype = new TextCell();
270 RawCell.prototype = new TextCell();
292
271
293 /** @method bind_events **/
272 /** @method bind_events **/
294 RawCell.prototype.bind_events = function () {
273 RawCell.prototype.bind_events = function () {
295 TextCell.prototype.bind_events.apply(this);
274 TextCell.prototype.bind_events.apply(this);
296 var that = this;
275 var that = this;
297 this.element.focusout(function() {
276 this.element.focusout(function() {
298 that.auto_highlight();
277 that.auto_highlight();
299 that.render();
278 that.render();
300 });
279 });
301
280
302 this.code_mirror.on('focus', function() { that.unrender(); });
281 this.code_mirror.on('focus', function() { that.unrender(); });
303 };
282 };
304
283
305 /**
284 /**
306 * Trigger autodetection of highlight scheme for current cell
285 * Trigger autodetection of highlight scheme for current cell
307 * @method auto_highlight
286 * @method auto_highlight
308 */
287 */
309 RawCell.prototype.auto_highlight = function () {
288 RawCell.prototype.auto_highlight = function () {
310 this._auto_highlight(this.config.raw_cell_highlight);
289 this._auto_highlight(this.config.raw_cell_highlight);
311 };
290 };
312
291
313 /** @method render **/
292 /** @method render **/
314 RawCell.prototype.render = function () {
293 RawCell.prototype.render = function () {
315 var cont = TextCell.prototype.render.apply(this);
294 var cont = TextCell.prototype.render.apply(this);
316 if (cont){
295 if (cont){
317 var text = this.get_text();
296 var text = this.get_text();
318 if (text === "") { text = this.placeholder; }
297 if (text === "") { text = this.placeholder; }
319 this.set_text(text);
298 this.set_text(text);
320 this.element.removeClass('rendered');
299 this.element.removeClass('rendered');
321 }
300 }
322 return cont;
301 return cont;
323 };
302 };
324
303
325
304
326 var HeadingCell = function (options) {
305 var HeadingCell = function (options) {
327 // Constructor
306 // Constructor
328 //
307 //
329 // Parameters:
308 // Parameters:
330 // options: dictionary
309 // options: dictionary
331 // Dictionary of keyword arguments.
310 // Dictionary of keyword arguments.
332 // events: $(Events) instance
311 // events: $(Events) instance
333 // config: dictionary
312 // config: dictionary
334 // keyboard_manager: KeyboardManager instance
313 // keyboard_manager: KeyboardManager instance
335 // notebook: Notebook instance
314 // notebook: Notebook instance
336 options = options || {};
315 options = options || {};
337 var config = utils.mergeopt(HeadingCell, options.config);
316 var config = utils.mergeopt(HeadingCell, options.config);
338 TextCell.apply(this, [$.extend({}, options, {config: config})]);
317 TextCell.apply(this, [$.extend({}, options, {config: config})]);
339
318
340 this.level = 1;
319 this.level = 1;
341 this.cell_type = 'heading';
320 this.cell_type = 'heading';
342 };
321 };
343
322
344 HeadingCell.options_default = {
323 HeadingCell.options_default = {
345 cm_config: {
324 cm_config: {
346 theme: 'heading-1'
325 theme: 'heading-1'
347 },
326 },
348 placeholder: "Type Heading Here"
327 placeholder: "Type Heading Here"
349 };
328 };
350
329
351 HeadingCell.prototype = new TextCell();
330 HeadingCell.prototype = new TextCell();
352
331
353 /** @method fromJSON */
332 /** @method fromJSON */
354 HeadingCell.prototype.fromJSON = function (data) {
333 HeadingCell.prototype.fromJSON = function (data) {
355 if (data.level !== undefined){
334 if (data.level !== undefined){
356 this.level = data.level;
335 this.level = data.level;
357 }
336 }
358 TextCell.prototype.fromJSON.apply(this, arguments);
337 TextCell.prototype.fromJSON.apply(this, arguments);
359 this.code_mirror.setOption("theme", "heading-"+this.level);
338 this.code_mirror.setOption("theme", "heading-"+this.level);
360 };
339 };
361
340
362
341
363 /** @method toJSON */
342 /** @method toJSON */
364 HeadingCell.prototype.toJSON = function () {
343 HeadingCell.prototype.toJSON = function () {
365 var data = TextCell.prototype.toJSON.apply(this);
344 var data = TextCell.prototype.toJSON.apply(this);
366 data.level = this.get_level();
345 data.level = this.get_level();
367 return data;
346 return data;
368 };
347 };
369
348
370 /**
349 /**
371 * Change heading level of cell, and re-render
350 * Change heading level of cell, and re-render
372 * @method set_level
351 * @method set_level
373 */
352 */
374 HeadingCell.prototype.set_level = function (level) {
353 HeadingCell.prototype.set_level = function (level) {
375 this.level = level;
354 this.level = level;
376 this.code_mirror.setOption("theme", "heading-"+level);
355 this.code_mirror.setOption("theme", "heading-"+level);
377
356
378 if (this.rendered) {
357 if (this.rendered) {
379 this.rendered = false;
358 this.rendered = false;
380 this.render();
359 this.render();
381 }
360 }
382 };
361 };
383
362
384 /** The depth of header cell, based on html (h1 to h6)
363 /** The depth of header cell, based on html (h1 to h6)
385 * @method get_level
364 * @method get_level
386 * @return {integer} level - for 1 to 6
365 * @return {integer} level - for 1 to 6
387 */
366 */
388 HeadingCell.prototype.get_level = function () {
367 HeadingCell.prototype.get_level = function () {
389 return this.level;
368 return this.level;
390 };
369 };
391
370
392
371
393 HeadingCell.prototype.get_rendered = function () {
372 HeadingCell.prototype.get_rendered = function () {
394 var r = this.element.find("div.text_cell_render");
373 var r = this.element.find("div.text_cell_render");
395 return r.children().first().html();
374 return r.children().first().html();
396 };
375 };
397
376
398 HeadingCell.prototype.render = function () {
377 HeadingCell.prototype.render = function () {
399 var cont = TextCell.prototype.render.apply(this);
378 var cont = TextCell.prototype.render.apply(this);
400 if (cont) {
379 if (cont) {
401 var text = this.get_text();
380 var text = this.get_text();
402 var math = null;
381 var math = null;
403 // Markdown headings must be a single line
382 // Markdown headings must be a single line
404 text = text.replace(/\n/g, ' ');
383 text = text.replace(/\n/g, ' ');
405 if (text === "") { text = this.placeholder; }
384 if (text === "") { text = this.placeholder; }
406 text = new Array(this.level + 1).join("#") + " " + text;
385 text = new Array(this.level + 1).join("#") + " " + text;
407 var text_and_math = mathjaxutils.remove_math(text);
386 var text_and_math = mathjaxutils.remove_math(text);
408 text = text_and_math[0];
387 text = text_and_math[0];
409 math = text_and_math[1];
388 math = text_and_math[1];
410 var html = marked.parser(marked.lexer(text));
389 var html = marked.parser(marked.lexer(text));
411 html = mathjaxutils.replace_math(html, math);
390 html = mathjaxutils.replace_math(html, math);
412 html = security.sanitize_html(html);
391 html = security.sanitize_html(html);
413 var h = $($.parseHTML(html));
392 var h = $($.parseHTML(html));
414 // add id and linkback anchor
393 // add id and linkback anchor
415 var hash = h.text().trim().replace(/ /g, '-');
394 var hash = h.text().trim().replace(/ /g, '-');
416 h.attr('id', hash);
395 h.attr('id', hash);
417 h.append(
396 h.append(
418 $('<a/>')
397 $('<a/>')
419 .addClass('anchor-link')
398 .addClass('anchor-link')
420 .attr('href', '#' + hash)
399 .attr('href', '#' + hash)
421 .text('¶')
400 .text('¶')
422 );
401 );
423 this.set_rendered(h);
402 this.set_rendered(h);
424 this.typeset();
403 this.typeset();
425 }
404 }
426 return cont;
405 return cont;
427 };
406 };
428
407
429 // Backwards compatability.
408 // Backwards compatability.
430 IPython.TextCell = TextCell;
409 IPython.TextCell = TextCell;
431 IPython.MarkdownCell = MarkdownCell;
410 IPython.MarkdownCell = MarkdownCell;
432 IPython.RawCell = RawCell;
411 IPython.RawCell = RawCell;
433 IPython.HeadingCell = HeadingCell;
412 IPython.HeadingCell = HeadingCell;
434
413
435 var textcell = {
414 var textcell = {
436 'TextCell': TextCell,
415 'TextCell': TextCell,
437 'MarkdownCell': MarkdownCell,
416 'MarkdownCell': MarkdownCell,
438 'RawCell': RawCell,
417 'RawCell': RawCell,
439 'HeadingCell': HeadingCell,
418 'HeadingCell': HeadingCell,
440 };
419 };
441 return textcell;
420 return textcell;
442 });
421 });
General Comments 0
You need to be logged in to leave comments. Login now