cell.js
481 lines
| 14.5 KiB
| application/javascript
|
JavascriptLexer
|
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. | ||||
//---------------------------------------------------------------------------- | ||||
|
r4349 | |||
//============================================================================ | ||||
// Cell | ||||
//============================================================================ | ||||
|
r8713 | /** | ||
* An extendable module that provide base functionnality to create cell for notebook. | ||||
|
r8739 | * @module IPython | ||
* @namespace IPython | ||||
* @submodule Cell | ||||
|
r8713 | */ | ||
|
r4349 | |||
|
r4352 | var IPython = (function (IPython) { | ||
|
r12103 | "use strict"; | ||
|
r4349 | |||
|
r4352 | var utils = IPython.utils; | ||
|
r4349 | |||
|
r8713 | /** | ||
|
r8714 | * The Base `Cell` class from which to inherit | ||
|
r8713 | * @class Cell | ||
|
r9548 | **/ | ||
|
r5959 | |||
|
r8713 | /* | ||
* @constructor | ||||
|
r9537 | * | ||
|
r9548 | * @param {object|undefined} [options] | ||
* @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters | ||||
|
r8713 | */ | ||
|
r9537 | var Cell = function (options) { | ||
|
r13668 | options = this.mergeopt(Cell, options); | ||
|
r9537 | // superclass default overwrite our default | ||
|
r13668 | |||
|
r10165 | this.placeholder = options.placeholder || ''; | ||
this.read_only = options.cm_config.readOnly; | ||||
|
r4352 | this.selected = false; | ||
|
r14014 | this.rendered = false; | ||
this.mode = 'command'; | ||||
|
r7523 | this.metadata = {}; | ||
|
r8202 | // load this from metadata later ? | ||
|
r8839 | this.user_highlight = 'auto'; | ||
|
r10165 | this.cm_config = options.cm_config; | ||
|
r13574 | this.cell_id = utils.uuid(); | ||
this._options = options; | ||||
|
r15495 | // For JS VM engines optimization, attributes should be all set (even | ||
|
r13574 | // 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; | ||||
|
r13676 | this.cell_type = this.cell_type || null; | ||
|
r13574 | this.code_mirror = null; | ||
|
r4352 | this.create_element(); | ||
|
r4529 | if (this.element !== null) { | ||
|
r4352 | this.element.data("cell", this); | ||
this.bind_events(); | ||||
|
r14049 | this.init_classes(); | ||
|
r4352 | } | ||
}; | ||||
|
r10165 | Cell.options_default = { | ||
cm_config : { | ||||
|
r9537 | indentUnit : 4, | ||
|
r10165 | readOnly: false, | ||
theme: "default" | ||||
} | ||||
|
r9537 | }; | ||
|
r10791 | |||
// FIXME: Workaround CM Bug #332 (Safari segfault on drag) | ||||
// by disabling drag/drop altogether on Safari | ||||
|
r15497 | // https://github.com/marijnh/CodeMirror/issues/332 | ||
|
r10791 | if (utils.browser[0] == "Safari") { | ||
Cell.options_default.cm_config.dragDrop = false; | ||||
} | ||||
|
r9537 | |||
|
r10165 | Cell.prototype.mergeopt = function(_class, options, overwrite){ | ||
|
r13668 | options = options || {}; | ||
|
r10791 | overwrite = overwrite || {}; | ||
|
r15497 | return $.extend(true, {}, _class.options_default, options, overwrite); | ||
}; | ||||
|
r5959 | |||
|
r8713 | /** | ||
* Empty. Subclasses must implement create_element. | ||||
|
r8714 | * This should contain all the code to create the DOM element in notebook | ||
|
r8713 | * and will be called by Base Class constructor. | ||
* @method create_element | ||||
*/ | ||||
|
r9073 | Cell.prototype.create_element = function () { | ||
}; | ||||
|
r4352 | |||
|
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'); | ||||
} | ||||
|
r15493 | }; | ||
|
r14049 | |||
|
r8713 | /** | ||
|
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. | ||||
|
r8713 | * @method bind_events | ||
*/ | ||||
|
r4352 | Cell.prototype.bind_events = function () { | ||
var that = this; | ||||
|
r7168 | // We trigger events so that Cell doesn't have to depend on Notebook. | ||
|
r4352 | that.element.click(function (event) { | ||
|
r14033 | if (!that.selected) { | ||
|
r7168 | $([IPython.events]).trigger('select.Cell', {'cell':that}); | ||
|
r15493 | } | ||
|
r4352 | }); | ||
that.element.focusin(function (event) { | ||||
|
r14033 | if (!that.selected) { | ||
|
r7168 | $([IPython.events]).trigger('select.Cell', {'cell':that}); | ||
|
r15493 | } | ||
|
r14016 | }); | ||
|
r10785 | if (this.code_mirror) { | ||
this.code_mirror.on("change", function(cm, change) { | ||||
$([IPython.events]).trigger("set_dirty.Notebook", {value: true}); | ||||
}); | ||||
|
r14092 | } | ||
|
r14015 | if (this.code_mirror) { | ||
this.code_mirror.on('focus', function(cm, change) { | ||||
|
r15501 | $([IPython.events]).trigger('edit_mode.Cell', {cell: that}); | ||
|
r14015 | }); | ||
|
r14092 | } | ||
|
r14033 | if (this.code_mirror) { | ||
this.code_mirror.on('blur', function(cm, change) { | ||||
|
r15777 | $([IPython.events]).trigger('command_mode.Cell', {cell: that}); | ||
|
r14033 | }); | ||
|
r14092 | } | ||
|
r4352 | }; | ||
|
r8714 | /** | ||
* Triger typsetting of math by mathjax on current cell element | ||||
* @method typeset | ||||
*/ | ||||
|
r8660 | Cell.prototype.typeset = function () { | ||
|
r14014 | if (window.MathJax) { | ||
|
r8662 | var cell_math = this.element.get(0); | ||
|
r10165 | MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]); | ||
|
r14092 | } | ||
|
r8660 | }; | ||
|
r5959 | |||
|
r8714 | /** | ||
|
r14013 | * handle cell level logic when a cell is selected | ||
|
r8714 | * @method select | ||
|
r14014 | * @return is the action being taken | ||
|
r8714 | */ | ||
|
r5943 | Cell.prototype.select = function () { | ||
|
r14014 | if (!this.selected) { | ||
this.element.addClass('selected'); | ||||
this.element.removeClass('unselected'); | ||||
this.selected = true; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
|
r14092 | } | ||
|
r5943 | }; | ||
|
r8714 | /** | ||
|
r14013 | * handle cell level logic when a cell is unselected | ||
|
r8714 | * @method unselect | ||
|
r14014 | * @return is the action being taken | ||
|
r8714 | */ | ||
|
r5943 | Cell.prototype.unselect = function () { | ||
|
r14014 | if (this.selected) { | ||
this.element.addClass('unselected'); | ||||
this.element.removeClass('selected'); | ||||
this.selected = false; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
|
r14092 | } | ||
|
r5943 | }; | ||
|
r8739 | /** | ||
|
r14014 | * handle cell level logic when a cell is rendered | ||
* @method render | ||||
* @return is the action being taken | ||||
|
r14013 | */ | ||
|
r14014 | Cell.prototype.render = function () { | ||
if (!this.rendered) { | ||||
this.element.addClass('rendered'); | ||||
this.element.removeClass('unrendered'); | ||||
this.rendered = true; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
|
r14092 | } | ||
|
r14013 | }; | ||
/** | ||||
|
r14014 | * handle cell level logic when a cell is unrendered | ||
* @method unrender | ||||
* @return is the action being taken | ||||
|
r14013 | */ | ||
|
r14014 | Cell.prototype.unrender = function () { | ||
if (this.rendered) { | ||||
this.element.addClass('unrendered'); | ||||
this.element.removeClass('rendered'); | ||||
this.rendered = false; | ||||
return true; | ||||
} else { | ||||
return false; | ||||
|
r14092 | } | ||
|
r14013 | }; | ||
/** | ||||
|
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; | ||||
|
r14092 | } | ||
|
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; | ||||
|
r14092 | } | ||
|
r15493 | }; | ||
|
r15777 | |||
|
r15495 | /** | ||
|
r14014 | * Focus the cell in the DOM sense | ||
* @method focus_cell | ||||
*/ | ||||
Cell.prototype.focus_cell = function () { | ||||
this.element.focus(); | ||||
|
r15493 | }; | ||
|
r14014 | |||
/** | ||||
|
r15497 | * Focus the editor area so a user can type | ||
* | ||||
* NOTE: If codemirror is focused via a mouse click event, you don't want to | ||||
* call this because it will cause a page jump. | ||||
* @method focus_editor | ||||
*/ | ||||
Cell.prototype.focus_editor = function () { | ||||
|
r15540 | this.refresh(); | ||
|
r15497 | this.code_mirror.focus(); | ||
}; | ||||
/** | ||||
|
r8739 | * Refresh codemirror instance | ||
* @method refresh | ||||
*/ | ||||
|
r5943 | Cell.prototype.refresh = function () { | ||
this.code_mirror.refresh(); | ||||
}; | ||||
|
r8739 | /** | ||
* should be overritten by subclass | ||||
|
r14015 | * @method get_text | ||
*/ | ||||
Cell.prototype.get_text = function () { | ||||
|
r5943 | }; | ||
|
r8739 | /** | ||
* should be overritten by subclass | ||||
|
r14015 | * @method set_text | ||
* @param {string} text | ||||
*/ | ||||
Cell.prototype.set_text = function (text) { | ||||
|
r5943 | }; | ||
|
r8739 | /** | ||
* should be overritten by subclass | ||||
* serialise cell to json. | ||||
* @method toJSON | ||||
**/ | ||||
|
r5943 | Cell.prototype.toJSON = function () { | ||
|
r7523 | var data = {}; | ||
data.metadata = this.metadata; | ||||
|
r13676 | data.cell_type = this.cell_type; | ||
|
r7523 | return data; | ||
|
r5943 | }; | ||
|
r8739 | /** | ||
* should be overritten by subclass | ||||
* @method fromJSON | ||||
**/ | ||||
|
r5943 | Cell.prototype.fromJSON = function (data) { | ||
|
r7523 | if (data.metadata !== undefined) { | ||
this.metadata = data.metadata; | ||||
} | ||||
|
r9064 | this.celltoolbar.rebuild(); | ||
|
r5943 | }; | ||
|
r8739 | /** | ||
|
r12509 | * can the cell be split into two cells | ||
|
r8739 | * @method is_splittable | ||
**/ | ||||
|
r5946 | Cell.prototype.is_splittable = function () { | ||
return true; | ||||
}; | ||||
|
r8739 | /** | ||
|
r12509 | * can the cell be merged with other cells | ||
* @method is_mergeable | ||||
**/ | ||||
Cell.prototype.is_mergeable = function () { | ||||
return true; | ||||
}; | ||||
/** | ||||
|
r8739 | * @return {String} - the text before the cursor | ||
* @method get_pre_cursor | ||||
**/ | ||||
|
r5946 | Cell.prototype.get_pre_cursor = function () { | ||
var cursor = this.code_mirror.getCursor(); | ||||
|
r10165 | var text = this.code_mirror.getRange({line:0, ch:0}, cursor); | ||
|
r5946 | text = text.replace(/^\n+/, '').replace(/\n+$/, ''); | ||
return text; | ||||
|
r15497 | }; | ||
|
r5946 | |||
|
r8739 | /** | ||
* @return {String} - the text after the cursor | ||||
* @method get_post_cursor | ||||
**/ | ||||
|
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; | ||||
|
r15497 | var end = {line:last_line_num, ch:last_line_len}; | ||
|
r5946 | var text = this.code_mirror.getRange(cursor, end); | ||
text = text.replace(/^\n+/, '').replace(/\n+$/, ''); | ||||
return text; | ||||
}; | ||||
|
r8739 | /** | ||
|
r9542 | * Show/Hide CodeMirror LineNumber | ||
|
r9690 | * @method show_line_numbers | ||
|
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(); | ||||
}; | ||||
/** | ||||
|
r8739 | * Toggle CodeMirror LineNumber | ||
* @method toggle_line_numbers | ||||
**/ | ||||
|
r6059 | Cell.prototype.toggle_line_numbers = function () { | ||
|
r9690 | var val = this.code_mirror.getOption('lineNumbers'); | ||
this.show_line_numbers(!val); | ||||
|
r6059 | }; | ||
|
r8739 | /** | ||
|
r9548 | * Force codemirror highlight mode | ||
|
r8739 | * @method force_highlight | ||
* @param {object} - CodeMirror mode | ||||
**/ | ||||
|
r8202 | Cell.prototype.force_highlight = function(mode) { | ||
this.user_highlight = mode; | ||||
this.auto_highlight(); | ||||
}; | ||||
|
r8739 | /** | ||
* Try to autodetect cell highlight mode, or use selected mode | ||||
* @methods _auto_highlight | ||||
* @private | ||||
* @param {String|object|undefined} - CodeMirror mode | 'auto' | ||||
**/ | ||||
|
r8202 | Cell.prototype._auto_highlight = function (modes) { | ||
//Here we handle manually selected modes | ||||
|
r15497 | var mode; | ||
if( this.user_highlight !== undefined && this.user_highlight != 'auto' ) | ||||
|
r8202 | { | ||
|
r15497 | mode = this.user_highlight; | ||
|
r8202 | CodeMirror.autoLoadMode(this.code_mirror, mode); | ||
this.code_mirror.setOption('mode', mode); | ||||
return; | ||||
} | ||||
|
r13573 | var current_mode = this.code_mirror.getOption('mode', mode); | ||
|
r8202 | var first_line = this.code_mirror.getLine(0); | ||
// loop on every pairs | ||||
|
r15497 | for(mode in modes) { | ||
var regs = modes[mode].reg; | ||||
|
r8202 | // only one key every time but regexp can't be keys... | ||
|
r14106 | for(var i=0; i<regs.length; i++) { | ||
|
r8202 | // here we handle non magic_modes | ||
|
r15497 | if(first_line.match(regs[i]) !== null) { | ||
|
r13573 | if(current_mode == mode){ | ||
return; | ||||
} | ||||
|
r15497 | if (mode.search('magic_') !== 0) { | ||
|
r10165 | this.code_mirror.setOption('mode', mode); | ||
|
r8202 | CodeMirror.autoLoadMode(this.code_mirror, mode); | ||
return; | ||||
} | ||||
|
r15497 | var open = modes[mode].open || "%%"; | ||
var close = modes[mode].close || "%%end"; | ||||
|
r8202 | var mmode = mode; | ||
mode = mmode.substr(6); | ||||
|
r13573 | if(current_mode == mode){ | ||
return; | ||||
} | ||||
|
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; | ||||
} | ||||
} | ||||
} | ||||
|
r12233 | // fallback on default | ||
|
r15497 | var default_mode; | ||
|
r12237 | try { | ||
default_mode = this._options.cm_config.mode; | ||||
} catch(e) { | ||||
default_mode = 'text/plain'; | ||||
} | ||||
|
r13573 | if( current_mode === default_mode){ | ||
|
r15497 | return; | ||
|
r13573 | } | ||
|
r8202 | this.code_mirror.setOption('mode', default_mode); | ||
}; | ||||
|
r6059 | |||
|
r4352 | IPython.Cell = Cell; | ||
return IPython; | ||||
}(IPython)); | ||||
|
r4349 | |||