//----------------------------------------------------------------------------1// Copyright (C) 2008-2011 The IPython Development Team2//3// Distributed under the terms of the BSD License. The full license is in4// the file COPYING, distributed as part of this software.5//----------------------------------------------------------------------------67//============================================================================8// Cell9//============================================================================10/**11* An extendable module that provide base functionnality to create cell for notebook.12* @module IPython13* @namespace IPython14* @submodule Cell15*/1617var IPython = (function (IPython) {18"use strict";1920var utils = IPython.utils;21var keycodes = IPython.keyboard.keycodes;2223/**24* The Base `Cell` class from which to inherit25* @class Cell26**/2728/*29* @constructor30*31* @param {object|undefined} [options]32* @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters33*/34var Cell = function (options) {3536options = this.mergeopt(Cell, options);37// superclass default overwrite our default3839this.placeholder = options.placeholder || '';40this.read_only = options.cm_config.readOnly;41this.selected = false;42this.rendered = false;43this.mode = 'command';44this.metadata = {};45// load this from metadata later ?46this.user_highlight = 'auto';47this.cm_config = options.cm_config;48this.cell_id = utils.uuid();49this._options = options;5051// For JS VM engines optimization, attributes should be all set (even52// to null) in the constructor, and if possible, if different subclass53// have new attributes with same name, they should be created in the54// same order. Easiest is to create and set to null in parent class.5556this.element = null;57this.cell_type = this.cell_type || null;58this.code_mirror = null;5960this.create_element();61if (this.element !== null) {62this.element.data("cell", this);63this.bind_events();64this.init_classes();65}66};6768Cell.options_default = {69cm_config : {70indentUnit : 4,71readOnly: false,72theme: "default",73extraKeys: {74"Cmd-Right":"goLineRight",75"End":"goLineRight",76"Cmd-Left":"goLineLeft"77}78}79};8081// FIXME: Workaround CM Bug #332 (Safari segfault on drag)82// by disabling drag/drop altogether on Safari83// https://github.com/marijnh/CodeMirror/issues/33284if (utils.browser[0] == "Safari") {85Cell.options_default.cm_config.dragDrop = false;86}8788Cell.prototype.mergeopt = function(_class, options, overwrite){89options = options || {};90overwrite = overwrite || {};91return $.extend(true, {}, _class.options_default, options, overwrite);92};9394/**95* Empty. Subclasses must implement create_element.96* This should contain all the code to create the DOM element in notebook97* and will be called by Base Class constructor.98* @method create_element99*/100Cell.prototype.create_element = function () {101};102103Cell.prototype.init_classes = function () {104// Call after this.element exists to initialize the css classes105// related to selected, rendered and mode.106if (this.selected) {107this.element.addClass('selected');108} else {109this.element.addClass('unselected');110}111if (this.rendered) {112this.element.addClass('rendered');113} else {114this.element.addClass('unrendered');115}116if (this.mode === 'edit') {117this.element.addClass('edit_mode');118} else {119this.element.addClass('command_mode');120}121};122123/**124* Subclasses can implement override bind_events.125* Be carefull to call the parent method when overwriting as it fires event.126* this will be triggerd after create_element in constructor.127* @method bind_events128*/129Cell.prototype.bind_events = function () {130var that = this;131// We trigger events so that Cell doesn't have to depend on Notebook.132that.element.click(function (event) {133if (!that.selected) {134$([IPython.events]).trigger('select.Cell', {'cell':that});135}136});137that.element.focusin(function (event) {138if (!that.selected) {139$([IPython.events]).trigger('select.Cell', {'cell':that});140}141});142if (this.code_mirror) {143this.code_mirror.on("change", function(cm, change) {144$([IPython.events]).trigger("set_dirty.Notebook", {value: true});145});146}147if (this.code_mirror) {148this.code_mirror.on('focus', function(cm, change) {149$([IPython.events]).trigger('edit_mode.Cell', {cell: that});150});151}152if (this.code_mirror) {153this.code_mirror.on('blur', function(cm, change) {154$([IPython.events]).trigger('command_mode.Cell', {cell: that});155});156}157};158159/**160* This method gets called in CodeMirror's onKeyDown/onKeyPress161* handlers and is used to provide custom key handling.162*163* To have custom handling, subclasses should override this method, but still call it164* in order to process the Edit mode keyboard shortcuts.165*166* @method handle_codemirror_keyevent167* @param {CodeMirror} editor - The codemirror instance bound to the cell168* @param {event} event - key press event which either should or should not be handled by CodeMirror169* @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise170*/171Cell.prototype.handle_codemirror_keyevent = function (editor, event) {172var that = this;173var shortcuts = IPython.keyboard_manager.edit_shortcuts;174175// if this is an edit_shortcuts shortcut, the global keyboard/shortcut176// manager will handle it177if (shortcuts.handles(event)) { return true; }178179return false;180};181182183/**184* Triger typsetting of math by mathjax on current cell element185* @method typeset186*/187Cell.prototype.typeset = function () {188if (window.MathJax) {189var cell_math = this.element.get(0);190MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);191}192};193194/**195* handle cell level logic when a cell is selected196* @method select197* @return is the action being taken198*/199Cell.prototype.select = function () {200if (!this.selected) {201this.element.addClass('selected');202this.element.removeClass('unselected');203this.selected = true;204return true;205} else {206return false;207}208};209210/**211* handle cell level logic when a cell is unselected212* @method unselect213* @return is the action being taken214*/215Cell.prototype.unselect = function () {216if (this.selected) {217this.element.addClass('unselected');218this.element.removeClass('selected');219this.selected = false;220return true;221} else {222return false;223}224};225226/**227* handle cell level logic when a cell is rendered228* @method render229* @return is the action being taken230*/231Cell.prototype.render = function () {232if (!this.rendered) {233this.element.addClass('rendered');234this.element.removeClass('unrendered');235this.rendered = true;236return true;237} else {238return false;239}240};241242/**243* handle cell level logic when a cell is unrendered244* @method unrender245* @return is the action being taken246*/247Cell.prototype.unrender = function () {248if (this.rendered) {249this.element.addClass('unrendered');250this.element.removeClass('rendered');251this.rendered = false;252return true;253} else {254return false;255}256};257258/**259* Delegates keyboard shortcut handling to either IPython keyboard260* manager when in command mode, or CodeMirror when in edit mode261*262* @method handle_keyevent263* @param {CodeMirror} editor - The codemirror instance bound to the cell264* @param {event} - key event to be handled265* @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise266*/267Cell.prototype.handle_keyevent = function (editor, event) {268269// console.log('CM', this.mode, event.which, event.type)270271if (this.mode === 'command') {272return true;273} else if (this.mode === 'edit') {274return this.handle_codemirror_keyevent(editor, event);275}276};277278/**279* @method at_top280* @return {Boolean}281*/282Cell.prototype.at_top = function () {283var cm = this.code_mirror;284var cursor = cm.getCursor();285if (cursor.line === 0 && cursor.ch === 0) {286return true;287}288return false;289};290291/**292* @method at_bottom293* @return {Boolean}294* */295Cell.prototype.at_bottom = function () {296var cm = this.code_mirror;297var cursor = cm.getCursor();298if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {299return true;300}301return false;302};303304/**305* enter the command mode for the cell306* @method command_mode307* @return is the action being taken308*/309Cell.prototype.command_mode = function () {310if (this.mode !== 'command') {311this.element.addClass('command_mode');312this.element.removeClass('edit_mode');313this.mode = 'command';314return true;315} else {316return false;317}318};319320/**321* enter the edit mode for the cell322* @method command_mode323* @return is the action being taken324*/325Cell.prototype.edit_mode = function () {326if (this.mode !== 'edit') {327this.element.addClass('edit_mode');328this.element.removeClass('command_mode');329this.mode = 'edit';330return true;331} else {332return false;333}334};335336/**337* Focus the cell in the DOM sense338* @method focus_cell339*/340Cell.prototype.focus_cell = function () {341this.element.focus();342};343344/**345* Focus the editor area so a user can type346*347* NOTE: If codemirror is focused via a mouse click event, you don't want to348* call this because it will cause a page jump.349* @method focus_editor350*/351Cell.prototype.focus_editor = function () {352this.refresh();353this.code_mirror.focus();354};355356/**357* Refresh codemirror instance358* @method refresh359*/360Cell.prototype.refresh = function () {361this.code_mirror.refresh();362};363364/**365* should be overritten by subclass366* @method get_text367*/368Cell.prototype.get_text = function () {369};370371/**372* should be overritten by subclass373* @method set_text374* @param {string} text375*/376Cell.prototype.set_text = function (text) {377};378379/**380* should be overritten by subclass381* serialise cell to json.382* @method toJSON383**/384Cell.prototype.toJSON = function () {385var data = {};386data.metadata = this.metadata;387data.cell_type = this.cell_type;388return data;389};390391392/**393* should be overritten by subclass394* @method fromJSON395**/396Cell.prototype.fromJSON = function (data) {397if (data.metadata !== undefined) {398this.metadata = data.metadata;399}400this.celltoolbar.rebuild();401};402403404/**405* can the cell be split into two cells406* @method is_splittable407**/408Cell.prototype.is_splittable = function () {409return true;410};411412413/**414* can the cell be merged with other cells415* @method is_mergeable416**/417Cell.prototype.is_mergeable = function () {418return true;419};420421422/**423* @return {String} - the text before the cursor424* @method get_pre_cursor425**/426Cell.prototype.get_pre_cursor = function () {427var cursor = this.code_mirror.getCursor();428var text = this.code_mirror.getRange({line:0, ch:0}, cursor);429text = text.replace(/^\n+/, '').replace(/\n+$/, '');430return text;431};432433434/**435* @return {String} - the text after the cursor436* @method get_post_cursor437**/438Cell.prototype.get_post_cursor = function () {439var cursor = this.code_mirror.getCursor();440var last_line_num = this.code_mirror.lineCount()-1;441var last_line_len = this.code_mirror.getLine(last_line_num).length;442var end = {line:last_line_num, ch:last_line_len};443var text = this.code_mirror.getRange(cursor, end);444text = text.replace(/^\n+/, '').replace(/\n+$/, '');445return text;446};447448/**449* Show/Hide CodeMirror LineNumber450* @method show_line_numbers451*452* @param value {Bool} show (true), or hide (false) the line number in CodeMirror453**/454Cell.prototype.show_line_numbers = function (value) {455this.code_mirror.setOption('lineNumbers', value);456this.code_mirror.refresh();457};458459/**460* Toggle CodeMirror LineNumber461* @method toggle_line_numbers462**/463Cell.prototype.toggle_line_numbers = function () {464var val = this.code_mirror.getOption('lineNumbers');465this.show_line_numbers(!val);466};467468/**469* Force codemirror highlight mode470* @method force_highlight471* @param {object} - CodeMirror mode472**/473Cell.prototype.force_highlight = function(mode) {474this.user_highlight = mode;475this.auto_highlight();476};477478/**479* Try to autodetect cell highlight mode, or use selected mode480* @methods _auto_highlight481* @private482* @param {String|object|undefined} - CodeMirror mode | 'auto'483**/484Cell.prototype._auto_highlight = function (modes) {485//Here we handle manually selected modes486var mode;487if( this.user_highlight !== undefined && this.user_highlight != 'auto' )488{489mode = this.user_highlight;490CodeMirror.autoLoadMode(this.code_mirror, mode);491this.code_mirror.setOption('mode', mode);492return;493}494var current_mode = this.code_mirror.getOption('mode', mode);495var first_line = this.code_mirror.getLine(0);496// loop on every pairs497for(mode in modes) {498var regs = modes[mode].reg;499// only one key every time but regexp can't be keys...500for(var i=0; i<regs.length; i++) {501// here we handle non magic_modes502if(first_line.match(regs[i]) !== null) {503if(current_mode == mode){504return;505}506if (mode.search('magic_') !== 0) {507this.code_mirror.setOption('mode', mode);508CodeMirror.autoLoadMode(this.code_mirror, mode);509return;510}511var open = modes[mode].open || "%%";512var close = modes[mode].close || "%%end";513var mmode = mode;514mode = mmode.substr(6);515if(current_mode == mode){516return;517}518CodeMirror.autoLoadMode(this.code_mirror, mode);519// create on the fly a mode that swhitch between520// plain/text and smth else otherwise `%%` is521// source of some highlight issues.522// we use patchedGetMode to circumvent a bug in CM523CodeMirror.defineMode(mmode , function(config) {524return CodeMirror.multiplexingMode(525CodeMirror.patchedGetMode(config, 'text/plain'),526// always set someting on close527{open: open, close: close,528mode: CodeMirror.patchedGetMode(config, mode),529delimStyle: "delimit"530}531);532});533this.code_mirror.setOption('mode', mmode);534return;535}536}537}538// fallback on default539var default_mode;540try {541default_mode = this._options.cm_config.mode;542} catch(e) {543default_mode = 'text/plain';544}545if( current_mode === default_mode){546return;547}548this.code_mirror.setOption('mode', default_mode);549};550551IPython.Cell = Cell;552553return IPython;554555}(IPython));556557558559