|
|
//----------------------------------------------------------------------------
|
|
|
// 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.
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
//============================================================================
|
|
|
// Cell
|
|
|
//============================================================================
|
|
|
/**
|
|
|
* An extendable module that provide base functionnality to create cell for notebook.
|
|
|
* @module IPython
|
|
|
* @namespace IPython
|
|
|
* @submodule Cell
|
|
|
*/
|
|
|
|
|
|
var IPython = (function (IPython) {
|
|
|
"use strict";
|
|
|
|
|
|
var utils = IPython.utils;
|
|
|
|
|
|
/**
|
|
|
* The Base `Cell` class from which to inherit
|
|
|
* @class Cell
|
|
|
**/
|
|
|
|
|
|
/*
|
|
|
* @constructor
|
|
|
*
|
|
|
* @param {object|undefined} [options]
|
|
|
* @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
|
|
|
*/
|
|
|
var Cell = function (options) {
|
|
|
|
|
|
options = this.mergeopt(Cell, options)
|
|
|
// superclass default overwrite our default
|
|
|
|
|
|
this.placeholder = options.placeholder || '';
|
|
|
this.read_only = options.cm_config.readOnly;
|
|
|
this.selected = false;
|
|
|
this.element = null;
|
|
|
this.metadata = {};
|
|
|
// load this from metadata later ?
|
|
|
this.user_highlight = 'auto';
|
|
|
this.cm_config = options.cm_config;
|
|
|
this.create_element();
|
|
|
if (this.element !== null) {
|
|
|
this.element.data("cell", this);
|
|
|
this.bind_events();
|
|
|
}
|
|
|
this.cell_id = utils.uuid();
|
|
|
this._options = options;
|
|
|
};
|
|
|
|
|
|
Cell.options_default = {
|
|
|
cm_config : {
|
|
|
indentUnit : 4,
|
|
|
readOnly: false,
|
|
|
theme: "default"
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 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;
|
|
|
}
|
|
|
|
|
|
Cell.prototype.mergeopt = function(_class, options, overwrite){
|
|
|
overwrite = overwrite || {};
|
|
|
return $.extend(true, {}, _class.options_default, options, overwrite)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Empty. Subclasses must implement create_element.
|
|
|
* This should contain all the code to create the DOM element in notebook
|
|
|
* and will be called by Base Class constructor.
|
|
|
* @method create_element
|
|
|
*/
|
|
|
Cell.prototype.create_element = function () {
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 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.
|
|
|
* @method bind_events
|
|
|
*/
|
|
|
Cell.prototype.bind_events = function () {
|
|
|
var that = this;
|
|
|
// We trigger events so that Cell doesn't have to depend on Notebook.
|
|
|
that.element.click(function (event) {
|
|
|
if (that.selected === false) {
|
|
|
$([IPython.events]).trigger('select.Cell', {'cell':that});
|
|
|
}
|
|
|
});
|
|
|
that.element.focusin(function (event) {
|
|
|
if (that.selected === false) {
|
|
|
$([IPython.events]).trigger('select.Cell', {'cell':that});
|
|
|
}
|
|
|
});
|
|
|
if (this.code_mirror) {
|
|
|
this.code_mirror.on("change", function(cm, change) {
|
|
|
$([IPython.events]).trigger("set_dirty.Notebook", {value: true});
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Triger typsetting of math by mathjax on current cell element
|
|
|
* @method typeset
|
|
|
*/
|
|
|
Cell.prototype.typeset = function () {
|
|
|
if (window.MathJax){
|
|
|
var cell_math = this.element.get(0);
|
|
|
MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* should be triggerd when cell is selected
|
|
|
* @method select
|
|
|
*/
|
|
|
Cell.prototype.select = function () {
|
|
|
this.element.addClass('selected');
|
|
|
this.selected = true;
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* should be triggerd when cell is unselected
|
|
|
* @method unselect
|
|
|
*/
|
|
|
Cell.prototype.unselect = function () {
|
|
|
this.element.removeClass('selected');
|
|
|
this.selected = false;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* should be overritten by subclass
|
|
|
* @method get_text
|
|
|
*/
|
|
|
Cell.prototype.get_text = function () {
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* should be overritten by subclass
|
|
|
* @method set_text
|
|
|
* @param {string} text
|
|
|
*/
|
|
|
Cell.prototype.set_text = function (text) {
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Refresh codemirror instance
|
|
|
* @method refresh
|
|
|
*/
|
|
|
Cell.prototype.refresh = function () {
|
|
|
this.code_mirror.refresh();
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* should be overritten by subclass
|
|
|
* @method edit
|
|
|
**/
|
|
|
Cell.prototype.edit = function () {
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* should be overritten by subclass
|
|
|
* @method render
|
|
|
**/
|
|
|
Cell.prototype.render = function () {
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* should be overritten by subclass
|
|
|
* serialise cell to json.
|
|
|
* @method toJSON
|
|
|
**/
|
|
|
Cell.prototype.toJSON = function () {
|
|
|
var data = {};
|
|
|
data.metadata = this.metadata;
|
|
|
return data;
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* should be overritten by subclass
|
|
|
* @method fromJSON
|
|
|
**/
|
|
|
Cell.prototype.fromJSON = function (data) {
|
|
|
if (data.metadata !== undefined) {
|
|
|
this.metadata = data.metadata;
|
|
|
}
|
|
|
this.celltoolbar.rebuild();
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* can the cell be splitted in 2 cells.
|
|
|
* @method is_splittable
|
|
|
**/
|
|
|
Cell.prototype.is_splittable = function () {
|
|
|
return true;
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* @return {String} - the text before the cursor
|
|
|
* @method get_pre_cursor
|
|
|
**/
|
|
|
Cell.prototype.get_pre_cursor = function () {
|
|
|
var cursor = this.code_mirror.getCursor();
|
|
|
var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
|
|
|
text = text.replace(/^\n+/, '').replace(/\n+$/, '');
|
|
|
return text;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* @return {String} - the text after the cursor
|
|
|
* @method get_post_cursor
|
|
|
**/
|
|
|
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;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Show/Hide CodeMirror LineNumber
|
|
|
* @method show_line_numbers
|
|
|
*
|
|
|
* @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();
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Toggle CodeMirror LineNumber
|
|
|
* @method toggle_line_numbers
|
|
|
**/
|
|
|
Cell.prototype.toggle_line_numbers = function () {
|
|
|
var val = this.code_mirror.getOption('lineNumbers');
|
|
|
this.show_line_numbers(!val);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Force codemirror highlight mode
|
|
|
* @method force_highlight
|
|
|
* @param {object} - CodeMirror mode
|
|
|
**/
|
|
|
Cell.prototype.force_highlight = function(mode) {
|
|
|
this.user_highlight = mode;
|
|
|
this.auto_highlight();
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Try to autodetect cell highlight mode, or use selected mode
|
|
|
* @methods _auto_highlight
|
|
|
* @private
|
|
|
* @param {String|object|undefined} - CodeMirror mode | 'auto'
|
|
|
**/
|
|
|
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;
|
|
|
}
|
|
|
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...
|
|
|
for(var reg in regs ) {
|
|
|
// here we handle non magic_modes
|
|
|
if(first_line.match(regs[reg]) != null) {
|
|
|
if (mode.search('magic_') != 0) {
|
|
|
this.code_mirror.setOption('mode', mode);
|
|
|
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);
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
// fallback on default
|
|
|
var default_mode
|
|
|
try {
|
|
|
default_mode = this._options.cm_config.mode;
|
|
|
} catch(e) {
|
|
|
default_mode = 'text/plain';
|
|
|
}
|
|
|
this.code_mirror.setOption('mode', default_mode);
|
|
|
};
|
|
|
|
|
|
IPython.Cell = Cell;
|
|
|
|
|
|
return IPython;
|
|
|
|
|
|
}(IPython));
|
|
|
|
|
|
|