cell.js
494 lines
| 14.8 KiB
| application/javascript
|
JavascriptLexer
Brian E. Granger
|
r4609 | //---------------------------------------------------------------------------- | ||
// Copyright (C) 2008-2011 The IPython Development Team | ||||
// | ||||
// Distributed under the terms of the BSD License. The full license is in | ||||
// the file COPYING, distributed as part of this software. | ||||
//---------------------------------------------------------------------------- | ||||
Brian E. Granger
|
r4349 | |||
//============================================================================ | ||||
// Cell | ||||
//============================================================================ | ||||
Matthias BUSSONNIER
|
r8713 | /** | ||
* An extendable module that provide base functionnality to create cell for notebook. | ||||
Matthias BUSSONNIER
|
r8739 | * @module IPython | ||
* @namespace IPython | ||||
* @submodule Cell | ||||
Matthias BUSSONNIER
|
r8713 | */ | ||
Brian E. Granger
|
r4349 | |||
Brian E. Granger
|
r4352 | var IPython = (function (IPython) { | ||
Matthias BUSSONNIER
|
r12103 | "use strict"; | ||
Brian E. Granger
|
r4349 | |||
Brian E. Granger
|
r4352 | var utils = IPython.utils; | ||
Brian E. Granger
|
r4349 | |||
Matthias BUSSONNIER
|
r8713 | /** | ||
Matthias BUSSONNIER
|
r8714 | * The Base `Cell` class from which to inherit | ||
Matthias BUSSONNIER
|
r8713 | * @class Cell | ||
Matthias BUSSONNIER
|
r9548 | **/ | ||
Brian Granger
|
r5959 | |||
Matthias BUSSONNIER
|
r8713 | /* | ||
* @constructor | ||||
Matthias BUSSONNIER
|
r9537 | * | ||
Matthias BUSSONNIER
|
r9548 | * @param {object|undefined} [options] | ||
* @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters | ||||
Matthias BUSSONNIER
|
r8713 | */ | ||
Matthias BUSSONNIER
|
r9537 | var Cell = function (options) { | ||
MinRK
|
r13668 | options = this.mergeopt(Cell, options); | ||
Matthias BUSSONNIER
|
r9537 | // superclass default overwrite our default | ||
MinRK
|
r13668 | |||
Matthias BUSSONNIER
|
r10165 | this.placeholder = options.placeholder || ''; | ||
this.read_only = options.cm_config.readOnly; | ||||
Brian E. Granger
|
r4352 | this.selected = false; | ||
Brian E. Granger
|
r14014 | this.rendered = false; | ||
this.mode = 'command'; | ||||
MinRK
|
r7523 | this.metadata = {}; | ||
Matthias BUSSONNIER
|
r8202 | // load this from metadata later ? | ||
Mikhail Korobov
|
r8839 | this.user_highlight = 'auto'; | ||
Matthias BUSSONNIER
|
r10165 | this.cm_config = options.cm_config; | ||
Matthias BUSSONNIER
|
r13574 | this.cell_id = utils.uuid(); | ||
this._options = options; | ||||
// For JS VM engines optimisation, attributes should be all set (even | ||||
// to null) in the constructor, and if possible, if different subclass | ||||
// have new attributes with same name, they should be created in the | ||||
// same order. Easiest is to create and set to null in parent class. | ||||
this.element = null; | ||||
MinRK
|
r13676 | this.cell_type = this.cell_type || null; | ||
Matthias BUSSONNIER
|
r13574 | this.code_mirror = null; | ||
Brian E. Granger
|
r4352 | this.create_element(); | ||
Brian E. Granger
|
r4529 | if (this.element !== null) { | ||
Brian E. Granger
|
r4352 | this.element.data("cell", this); | ||
this.bind_events(); | ||||
Brian E. Granger
|
r14049 | this.init_classes(); | ||
Brian E. Granger
|
r4352 | } | ||
}; | ||||
Matthias BUSSONNIER
|
r10165 | Cell.options_default = { | ||
cm_config : { | ||||
Matthias BUSSONNIER
|
r9537 | indentUnit : 4, | ||
Matthias BUSSONNIER
|
r10165 | readOnly: false, | ||
theme: "default" | ||||
} | ||||
Matthias BUSSONNIER
|
r9537 | }; | ||
MinRK
|
r10791 | |||
// FIXME: Workaround CM Bug #332 (Safari segfault on drag) | ||||
// by disabling drag/drop altogether on Safari | ||||
// https://github.com/marijnh/CodeMirror/issues/332 | ||||
if (utils.browser[0] == "Safari") { | ||||
Cell.options_default.cm_config.dragDrop = false; | ||||
} | ||||
Matthias BUSSONNIER
|
r9537 | |||
Matthias BUSSONNIER
|
r10165 | Cell.prototype.mergeopt = function(_class, options, overwrite){ | ||
MinRK
|
r13668 | options = options || {}; | ||
MinRK
|
r10791 | overwrite = overwrite || {}; | ||
Matthias BUSSONNIER
|
r10165 | return $.extend(true, {}, _class.options_default, options, overwrite) | ||
} | ||||
Brian Granger
|
r5959 | |||
Matthias BUSSONNIER
|
r8713 | /** | ||
* Empty. Subclasses must implement create_element. | ||||
Matthias BUSSONNIER
|
r8714 | * This should contain all the code to create the DOM element in notebook | ||
Matthias BUSSONNIER
|
r8713 | * and will be called by Base Class constructor. | ||
* @method create_element | ||||
*/ | ||||
Matthias BUSSONNIER
|
r9073 | Cell.prototype.create_element = function () { | ||
}; | ||||
Brian E. Granger
|
r4352 | |||
Brian E. Granger
|
r14049 | Cell.prototype.init_classes = function () { | ||
// Call after this.element exists to initialize the css classes | ||||
// related to selected, rendered and mode. | ||||
if (this.selected) { | ||||
this.element.addClass('selected'); | ||||
} else { | ||||
this.element.addClass('unselected'); | ||||
} | ||||
if (this.rendered) { | ||||
this.element.addClass('rendered'); | ||||
} else { | ||||
this.element.addClass('unrendered'); | ||||
} | ||||
if (this.mode === 'edit') { | ||||
this.element.addClass('edit_mode'); | ||||
} else { | ||||
this.element.addClass('command_mode'); | ||||
} | ||||
} | ||||
Matthias BUSSONNIER
|
r8281 | |||
Matthias BUSSONNIER
|
r8713 | /** | ||
Matthias BUSSONNIER
|
r8714 | * Subclasses can implement override bind_events. | ||
* Be carefull to call the parent method when overwriting as it fires event. | ||||
* this will be triggerd after create_element in constructor. | ||||
Matthias BUSSONNIER
|
r8713 | * @method bind_events | ||
*/ | ||||
Brian E. Granger
|
r4352 | Cell.prototype.bind_events = function () { | ||
var that = this; | ||||
Brian Granger
|
r7168 | // We trigger events so that Cell doesn't have to depend on Notebook. | ||
Brian E. Granger
|
r4352 | that.element.click(function (event) { | ||
Brian E. Granger
|
r14033 | if (!that.selected) { | ||
Brian Granger
|
r7168 | $([IPython.events]).trigger('select.Cell', {'cell':that}); | ||
Brian E. Granger
|
r14016 | }; | ||
Brian E. Granger
|
r4352 | }); | ||
that.element.focusin(function (event) { | ||||
Brian E. Granger
|
r14033 | if (!that.selected) { | ||
Brian Granger
|
r7168 | $([IPython.events]).trigger('select.Cell', {'cell':that}); | ||
Brian E. Granger
|
r14016 | }; | ||
}); | ||||
MinRK
|
r10785 | if (this.code_mirror) { | ||
this.code_mirror.on("change", function(cm, change) { | ||||
$([IPython.events]).trigger("set_dirty.Notebook", {value: true}); | ||||
}); | ||||
Brian E. Granger
|
r14092 | } | ||
Brian E. Granger
|
r14015 | if (this.code_mirror) { | ||
this.code_mirror.on('focus', function(cm, change) { | ||||
$([IPython.events]).trigger('edit_mode.Cell', {cell: that}); | ||||
}); | ||||
Brian E. Granger
|
r14092 | } | ||
Brian E. Granger
|
r14033 | if (this.code_mirror) { | ||
this.code_mirror.on('blur', function(cm, change) { | ||||
if (that.mode === 'edit') { | ||||
setTimeout(function () { | ||||
var isf = IPython.utils.is_focused; | ||||
var trigger = true; | ||||
if (isf('div#tooltip') || isf('div.completions')) { | ||||
trigger = false; | ||||
} | ||||
if (trigger) { | ||||
$([IPython.events]).trigger('command_mode.Cell', {cell: that}); | ||||
} | ||||
}, 1); | ||||
} | ||||
}); | ||||
Brian E. Granger
|
r14092 | } | ||
Brian E. Granger
|
r4352 | }; | ||
Matthias BUSSONNIER
|
r8714 | /** | ||
* Triger typsetting of math by mathjax on current cell element | ||||
* @method typeset | ||||
*/ | ||||
Aron Ahmadia
|
r8660 | Cell.prototype.typeset = function () { | ||
Brian E. Granger
|
r14014 | if (window.MathJax) { | ||
Aron Ahmadia
|
r8662 | var cell_math = this.element.get(0); | ||
Matthias BUSSONNIER
|
r10165 | MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]); | ||
Brian E. Granger
|
r14092 | } | ||
Aron Ahmadia
|
r8660 | }; | ||
Brian Granger
|
r5959 | |||
Matthias BUSSONNIER
|
r8714 | /** | ||
Brian E. Granger
|
r14013 | * handle cell level logic when a cell is selected | ||
Matthias BUSSONNIER
|
r8714 | * @method select | ||
Brian E. Granger
|
r14014 | * @return is the action being taken | ||
Matthias BUSSONNIER
|
r8714 | */ | ||
Brian Granger
|
r5943 | Cell.prototype.select = function () { | ||
Brian E. Granger
|
r14014 | if (!this.selected) { | ||
this.element.addClass('selected'); | ||||
this.element.removeClass('unselected'); | ||||
this.selected = true; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
Brian E. Granger
|
r14092 | } | ||
Brian Granger
|
r5943 | }; | ||
Matthias BUSSONNIER
|
r8714 | /** | ||
Brian E. Granger
|
r14013 | * handle cell level logic when a cell is unselected | ||
Matthias BUSSONNIER
|
r8714 | * @method unselect | ||
Brian E. Granger
|
r14014 | * @return is the action being taken | ||
Matthias BUSSONNIER
|
r8714 | */ | ||
Brian Granger
|
r5943 | Cell.prototype.unselect = function () { | ||
Brian E. Granger
|
r14014 | if (this.selected) { | ||
this.element.addClass('unselected'); | ||||
this.element.removeClass('selected'); | ||||
this.selected = false; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
Brian E. Granger
|
r14092 | } | ||
Brian Granger
|
r5943 | }; | ||
Matthias BUSSONNIER
|
r8739 | /** | ||
Brian E. Granger
|
r14014 | * handle cell level logic when a cell is rendered | ||
* @method render | ||||
* @return is the action being taken | ||||
Brian E. Granger
|
r14013 | */ | ||
Brian E. Granger
|
r14014 | Cell.prototype.render = function () { | ||
if (!this.rendered) { | ||||
this.element.addClass('rendered'); | ||||
this.element.removeClass('unrendered'); | ||||
this.rendered = true; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
Brian E. Granger
|
r14092 | } | ||
Brian E. Granger
|
r14013 | }; | ||
/** | ||||
Brian E. Granger
|
r14014 | * handle cell level logic when a cell is unrendered | ||
* @method unrender | ||||
* @return is the action being taken | ||||
Brian E. Granger
|
r14013 | */ | ||
Brian E. Granger
|
r14014 | Cell.prototype.unrender = function () { | ||
if (this.rendered) { | ||||
this.element.addClass('unrendered'); | ||||
this.element.removeClass('rendered'); | ||||
this.rendered = false; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
Brian E. Granger
|
r14092 | } | ||
Brian E. Granger
|
r14013 | }; | ||
/** | ||||
Brian E. Granger
|
r14014 | * enter the command mode for the cell | ||
* @method command_mode | ||||
* @return is the action being taken | ||||
*/ | ||||
Cell.prototype.command_mode = function () { | ||||
if (this.mode !== 'command') { | ||||
this.element.addClass('command_mode'); | ||||
this.element.removeClass('edit_mode'); | ||||
this.mode = 'command'; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
Brian E. Granger
|
r14092 | } | ||
Brian E. Granger
|
r14014 | }; | ||
/** | ||||
* enter the edit mode for the cell | ||||
* @method command_mode | ||||
* @return is the action being taken | ||||
*/ | ||||
Cell.prototype.edit_mode = function () { | ||||
if (this.mode !== 'edit') { | ||||
this.element.addClass('edit_mode'); | ||||
this.element.removeClass('command_mode'); | ||||
this.mode = 'edit'; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
Brian E. Granger
|
r14092 | } | ||
Brian E. Granger
|
r14014 | } | ||
/** | ||||
* Focus the cell in the DOM sense | ||||
* @method focus_cell | ||||
*/ | ||||
Cell.prototype.focus_cell = function () { | ||||
this.element.focus(); | ||||
} | ||||
/** | ||||
* Focus the editor area so a user can type | ||||
* @method focus_editor | ||||
*/ | ||||
Cell.prototype.focus_editor = function () { | ||||
Brian E. Granger
|
r14015 | this.refresh(); | ||
Brian E. Granger
|
r14014 | this.code_mirror.focus(); | ||
} | ||||
/** | ||||
Matthias BUSSONNIER
|
r8739 | * Refresh codemirror instance | ||
* @method refresh | ||||
*/ | ||||
Brian Granger
|
r5943 | Cell.prototype.refresh = function () { | ||
this.code_mirror.refresh(); | ||||
}; | ||||
Matthias BUSSONNIER
|
r8739 | /** | ||
* should be overritten by subclass | ||||
Brian E. Granger
|
r14015 | * @method get_text | ||
*/ | ||||
Cell.prototype.get_text = function () { | ||||
Brian Granger
|
r5943 | }; | ||
Matthias BUSSONNIER
|
r8739 | /** | ||
* should be overritten by subclass | ||||
Brian E. Granger
|
r14015 | * @method set_text | ||
* @param {string} text | ||||
*/ | ||||
Cell.prototype.set_text = function (text) { | ||||
Brian Granger
|
r5943 | }; | ||
Matthias BUSSONNIER
|
r8739 | /** | ||
* should be overritten by subclass | ||||
* serialise cell to json. | ||||
* @method toJSON | ||||
**/ | ||||
Brian Granger
|
r5943 | Cell.prototype.toJSON = function () { | ||
MinRK
|
r7523 | var data = {}; | ||
data.metadata = this.metadata; | ||||
MinRK
|
r13676 | data.cell_type = this.cell_type; | ||
MinRK
|
r7523 | return data; | ||
Brian Granger
|
r5943 | }; | ||
Matthias BUSSONNIER
|
r8739 | /** | ||
* should be overritten by subclass | ||||
* @method fromJSON | ||||
**/ | ||||
Brian Granger
|
r5943 | Cell.prototype.fromJSON = function (data) { | ||
MinRK
|
r7523 | if (data.metadata !== undefined) { | ||
this.metadata = data.metadata; | ||||
} | ||||
Matthias BUSSONNIER
|
r9064 | this.celltoolbar.rebuild(); | ||
Brian Granger
|
r5943 | }; | ||
Matthias BUSSONNIER
|
r8739 | /** | ||
MinRK
|
r12509 | * can the cell be split into two cells | ||
Matthias BUSSONNIER
|
r8739 | * @method is_splittable | ||
**/ | ||||
Brian Granger
|
r5946 | Cell.prototype.is_splittable = function () { | ||
return true; | ||||
}; | ||||
Matthias BUSSONNIER
|
r8739 | /** | ||
MinRK
|
r12509 | * can the cell be merged with other cells | ||
* @method is_mergeable | ||||
**/ | ||||
Cell.prototype.is_mergeable = function () { | ||||
return true; | ||||
}; | ||||
/** | ||||
Matthias BUSSONNIER
|
r8739 | * @return {String} - the text before the cursor | ||
* @method get_pre_cursor | ||||
**/ | ||||
Brian Granger
|
r5946 | Cell.prototype.get_pre_cursor = function () { | ||
var cursor = this.code_mirror.getCursor(); | ||||
Matthias BUSSONNIER
|
r10165 | var text = this.code_mirror.getRange({line:0, ch:0}, cursor); | ||
Brian Granger
|
r5946 | text = text.replace(/^\n+/, '').replace(/\n+$/, ''); | ||
return text; | ||||
} | ||||
Matthias BUSSONNIER
|
r8739 | /** | ||
* @return {String} - the text after the cursor | ||||
* @method get_post_cursor | ||||
**/ | ||||
Brian Granger
|
r5946 | Cell.prototype.get_post_cursor = function () { | ||
var cursor = this.code_mirror.getCursor(); | ||||
var last_line_num = this.code_mirror.lineCount()-1; | ||||
var last_line_len = this.code_mirror.getLine(last_line_num).length; | ||||
var end = {line:last_line_num, ch:last_line_len} | ||||
var text = this.code_mirror.getRange(cursor, end); | ||||
text = text.replace(/^\n+/, '').replace(/\n+$/, ''); | ||||
return text; | ||||
}; | ||||
Matthias BUSSONNIER
|
r8739 | /** | ||
Matthias BUSSONNIER
|
r9542 | * Show/Hide CodeMirror LineNumber | ||
Matthias BUSSONNIER
|
r9690 | * @method show_line_numbers | ||
Matthias BUSSONNIER
|
r9542 | * | ||
* @param value {Bool} show (true), or hide (false) the line number in CodeMirror | ||||
**/ | ||||
Cell.prototype.show_line_numbers = function (value) { | ||||
this.code_mirror.setOption('lineNumbers', value); | ||||
this.code_mirror.refresh(); | ||||
}; | ||||
/** | ||||
Matthias BUSSONNIER
|
r8739 | * Toggle CodeMirror LineNumber | ||
* @method toggle_line_numbers | ||||
**/ | ||||
Brian Granger
|
r6059 | Cell.prototype.toggle_line_numbers = function () { | ||
Matthias BUSSONNIER
|
r9690 | var val = this.code_mirror.getOption('lineNumbers'); | ||
this.show_line_numbers(!val); | ||||
Brian Granger
|
r6059 | }; | ||
Matthias BUSSONNIER
|
r8739 | /** | ||
Matthias BUSSONNIER
|
r9548 | * Force codemirror highlight mode | ||
Matthias BUSSONNIER
|
r8739 | * @method force_highlight | ||
* @param {object} - CodeMirror mode | ||||
**/ | ||||
Matthias BUSSONNIER
|
r8202 | Cell.prototype.force_highlight = function(mode) { | ||
this.user_highlight = mode; | ||||
this.auto_highlight(); | ||||
}; | ||||
Matthias BUSSONNIER
|
r8739 | /** | ||
* Try to autodetect cell highlight mode, or use selected mode | ||||
* @methods _auto_highlight | ||||
* @private | ||||
* @param {String|object|undefined} - CodeMirror mode | 'auto' | ||||
**/ | ||||
Matthias BUSSONNIER
|
r8202 | Cell.prototype._auto_highlight = function (modes) { | ||
//Here we handle manually selected modes | ||||
if( this.user_highlight != undefined && this.user_highlight != 'auto' ) | ||||
{ | ||||
var mode = this.user_highlight; | ||||
CodeMirror.autoLoadMode(this.code_mirror, mode); | ||||
this.code_mirror.setOption('mode', mode); | ||||
return; | ||||
} | ||||
Matthias BUSSONNIER
|
r13573 | var current_mode = this.code_mirror.getOption('mode', mode); | ||
Matthias BUSSONNIER
|
r8202 | var first_line = this.code_mirror.getLine(0); | ||
// loop on every pairs | ||||
for( var mode in modes) { | ||||
var regs = modes[mode]['reg']; | ||||
// only one key every time but regexp can't be keys... | ||||
Sylvain Corlay
|
r14106 | for(var i=0; i<regs.length; i++) { | ||
Matthias BUSSONNIER
|
r8202 | // here we handle non magic_modes | ||
Sylvain Corlay
|
r14106 | if(first_line.match(regs[i]) != null) { | ||
Matthias BUSSONNIER
|
r13573 | if(current_mode == mode){ | ||
return; | ||||
} | ||||
Matthias BUSSONNIER
|
r8202 | if (mode.search('magic_') != 0) { | ||
Matthias BUSSONNIER
|
r10165 | this.code_mirror.setOption('mode', mode); | ||
Matthias BUSSONNIER
|
r8202 | CodeMirror.autoLoadMode(this.code_mirror, mode); | ||
return; | ||||
} | ||||
var open = modes[mode]['open']|| "%%"; | ||||
var close = modes[mode]['close']|| "%%end"; | ||||
var mmode = mode; | ||||
mode = mmode.substr(6); | ||||
Matthias BUSSONNIER
|
r13573 | if(current_mode == mode){ | ||
return; | ||||
} | ||||
Matthias BUSSONNIER
|
r8202 | CodeMirror.autoLoadMode(this.code_mirror, mode); | ||
// create on the fly a mode that swhitch between | ||||
// plain/text and smth else otherwise `%%` is | ||||
// source of some highlight issues. | ||||
// we use patchedGetMode to circumvent a bug in CM | ||||
CodeMirror.defineMode(mmode , function(config) { | ||||
return CodeMirror.multiplexingMode( | ||||
CodeMirror.patchedGetMode(config, 'text/plain'), | ||||
// always set someting on close | ||||
{open: open, close: close, | ||||
mode: CodeMirror.patchedGetMode(config, mode), | ||||
delimStyle: "delimit" | ||||
} | ||||
); | ||||
}); | ||||
this.code_mirror.setOption('mode', mmode); | ||||
return; | ||||
} | ||||
} | ||||
} | ||||
Matthias BUSSONNIER
|
r12233 | // fallback on default | ||
Matthias Bussonnier
|
r12237 | var default_mode | ||
try { | ||||
default_mode = this._options.cm_config.mode; | ||||
} catch(e) { | ||||
default_mode = 'text/plain'; | ||||
} | ||||
Matthias BUSSONNIER
|
r13573 | if( current_mode === default_mode){ | ||
return | ||||
} | ||||
Matthias BUSSONNIER
|
r8202 | this.code_mirror.setOption('mode', default_mode); | ||
}; | ||||
Brian Granger
|
r6059 | |||
Brian E. Granger
|
r4352 | IPython.Cell = Cell; | ||
return IPython; | ||||
}(IPython)); | ||||
Brian E. Granger
|
r4349 | |||