##// END OF EJS Templates
JS Configurablity Take 2...
Matthias BUSSONNIER -
Show More
@@ -1,334 +1,345 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Cell
10 10 //============================================================================
11 11 /**
12 12 * An extendable module that provide base functionnality to create cell for notebook.
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule Cell
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19
20 20 var utils = IPython.utils;
21 21
22 22 /**
23 23 * The Base `Cell` class from which to inherit
24 24 * @class Cell
25 25 **/
26 26
27 27 /*
28 28 * @constructor
29 29 *
30 30 * @param {object|undefined} [options]
31 31 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
32 32 */
33 33 var Cell = function (options) {
34 34
35 options = options || {};
35 options = this.mergeopt(Cell, options)
36 36 // superclass default overwrite our default
37 this.cm_config = $.extend({},Cell.cm_default,options.cm_config);
38 37
39 this.placeholder = this.placeholder || '';
40 this.read_only = false;
38 this.placeholder = options.placeholder || '';
39 this.read_only = options.cm_config.readOnly;
41 40 this.selected = false;
42 41 this.element = null;
43 42 this.metadata = {};
44 43 // load this from metadata later ?
45 44 this.user_highlight = 'auto';
45 this.cm_config = options.cm_config;
46 46 this.create_element();
47 47 if (this.element !== null) {
48 48 this.element.data("cell", this);
49 49 this.bind_events();
50 50 }
51 51 this.cell_id = utils.uuid();
52 this._options = options;
52 53 };
53 54
54 Cell.cm_default = {
55 Cell.options_default = {
56 cm_config : {
55 57 indentUnit : 4,
56 readOnly: this.read_only,
58 readOnly: false,
59 theme: "default"
60 }
57 61 };
58 62
63 Cell.prototype.mergeopt = function(_class, options, overwrite){
64 overwrite = overwrite ||Β {};
65 return $.extend(true, {}, _class.options_default, options, overwrite)
66
67 }
68
69
59 70
60 71 /**
61 72 * Empty. Subclasses must implement create_element.
62 73 * This should contain all the code to create the DOM element in notebook
63 74 * and will be called by Base Class constructor.
64 75 * @method create_element
65 76 */
66 77 Cell.prototype.create_element = function () {
67 78 };
68 79
69 80
70 81 /**
71 82 * Subclasses can implement override bind_events.
72 83 * Be carefull to call the parent method when overwriting as it fires event.
73 84 * this will be triggerd after create_element in constructor.
74 85 * @method bind_events
75 86 */
76 87 Cell.prototype.bind_events = function () {
77 88 var that = this;
78 89 // We trigger events so that Cell doesn't have to depend on Notebook.
79 90 that.element.click(function (event) {
80 91 if (that.selected === false) {
81 92 $([IPython.events]).trigger('select.Cell', {'cell':that});
82 93 }
83 94 });
84 95 that.element.focusin(function (event) {
85 96 if (that.selected === false) {
86 97 $([IPython.events]).trigger('select.Cell', {'cell':that});
87 98 }
88 99 });
89 100 };
90 101
91 102 /**
92 103 * Triger typsetting of math by mathjax on current cell element
93 104 * @method typeset
94 105 */
95 106 Cell.prototype.typeset = function () {
96 107 if (window.MathJax){
97 108 var cell_math = this.element.get(0);
98 MathJax.Hub.Queue(["Typeset",MathJax.Hub,cell_math]);
109 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
99 110 }
100 111 };
101 112
102 113 /**
103 114 * should be triggerd when cell is selected
104 115 * @method select
105 116 */
106 117 Cell.prototype.select = function () {
107 118 this.element.addClass('selected');
108 119 this.selected = true;
109 120 };
110 121
111 122
112 123 /**
113 124 * should be triggerd when cell is unselected
114 125 * @method unselect
115 126 */
116 127 Cell.prototype.unselect = function () {
117 128 this.element.removeClass('selected');
118 129 this.selected = false;
119 130 };
120 131
121 132 /**
122 133 * should be overritten by subclass
123 134 * @method get_text
124 135 */
125 136 Cell.prototype.get_text = function () {
126 137 };
127 138
128 139 /**
129 140 * should be overritten by subclass
130 141 * @method set_text
131 142 * @param {string} text
132 143 */
133 144 Cell.prototype.set_text = function (text) {
134 145 };
135 146
136 147 /**
137 148 * Refresh codemirror instance
138 149 * @method refresh
139 150 */
140 151 Cell.prototype.refresh = function () {
141 152 this.code_mirror.refresh();
142 153 };
143 154
144 155
145 156 /**
146 157 * should be overritten by subclass
147 158 * @method edit
148 159 **/
149 160 Cell.prototype.edit = function () {
150 161 };
151 162
152 163
153 164 /**
154 165 * should be overritten by subclass
155 166 * @method render
156 167 **/
157 168 Cell.prototype.render = function () {
158 169 };
159 170
160 171 /**
161 172 * should be overritten by subclass
162 173 * serialise cell to json.
163 174 * @method toJSON
164 175 **/
165 176 Cell.prototype.toJSON = function () {
166 177 var data = {};
167 178 data.metadata = this.metadata;
168 179 return data;
169 180 };
170 181
171 182
172 183 /**
173 184 * should be overritten by subclass
174 185 * @method fromJSON
175 186 **/
176 187 Cell.prototype.fromJSON = function (data) {
177 188 if (data.metadata !== undefined) {
178 189 this.metadata = data.metadata;
179 190 }
180 191 this.celltoolbar.rebuild();
181 192 };
182 193
183 194
184 195 /**
185 196 * can the cell be splitted in 2 cells.
186 197 * @method is_splittable
187 198 **/
188 199 Cell.prototype.is_splittable = function () {
189 200 return true;
190 201 };
191 202
192 203
193 204 /**
194 205 * @return {String} - the text before the cursor
195 206 * @method get_pre_cursor
196 207 **/
197 208 Cell.prototype.get_pre_cursor = function () {
198 209 var cursor = this.code_mirror.getCursor();
199 var text = this.code_mirror.getRange({line:0,ch:0}, cursor);
210 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
200 211 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
201 212 return text;
202 213 }
203 214
204 215
205 216 /**
206 217 * @return {String} - the text after the cursor
207 218 * @method get_post_cursor
208 219 **/
209 220 Cell.prototype.get_post_cursor = function () {
210 221 var cursor = this.code_mirror.getCursor();
211 222 var last_line_num = this.code_mirror.lineCount()-1;
212 223 var last_line_len = this.code_mirror.getLine(last_line_num).length;
213 224 var end = {line:last_line_num, ch:last_line_len}
214 225 var text = this.code_mirror.getRange(cursor, end);
215 226 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
216 227 return text;
217 228 };
218 229
219 230
220 231 /** Grow the cell by hand. This is used upon reloading from JSON, when the
221 232 * autogrow handler is not called.
222 233 *
223 234 * could be made static
224 235 *
225 236 * @param {Dom element} - element
226 237 * @method grow
227 238 **/
228 239 Cell.prototype.grow = function(element) {
229 240 var dom = element.get(0);
230 241 var lines_count = 0;
231 242 // modified split rule from
232 243 // http://stackoverflow.com/questions/2035910/how-to-get-the-number-of-lines-in-a-textarea/2036424#2036424
233 244 var lines = dom.value.split(/\r|\r\n|\n/);
234 245 lines_count = lines.length;
235 246 if (lines_count >= 1) {
236 247 dom.rows = lines_count;
237 248 } else {
238 249 dom.rows = 1;
239 250 }
240 251 };
241 252
242 253 /**
243 254 * Show/Hide CodeMirror LineNumber
244 255 * @method show_line_numbers
245 256 *
246 257 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
247 258 **/
248 259 Cell.prototype.show_line_numbers = function (value) {
249 260 this.code_mirror.setOption('lineNumbers', value);
250 261 this.code_mirror.refresh();
251 262 };
252 263
253 264 /**
254 265 * Toggle CodeMirror LineNumber
255 266 * @method toggle_line_numbers
256 267 **/
257 268 Cell.prototype.toggle_line_numbers = function () {
258 269 var val = this.code_mirror.getOption('lineNumbers');
259 270 this.show_line_numbers(!val);
260 271 };
261 272
262 273 /**
263 274 * Force codemirror highlight mode
264 275 * @method force_highlight
265 276 * @param {object} - CodeMirror mode
266 277 **/
267 278 Cell.prototype.force_highlight = function(mode) {
268 279 this.user_highlight = mode;
269 280 this.auto_highlight();
270 281 };
271 282
272 283 /**
273 284 * Try to autodetect cell highlight mode, or use selected mode
274 285 * @methods _auto_highlight
275 286 * @private
276 287 * @param {String|object|undefined} - CodeMirror mode | 'auto'
277 288 **/
278 289 Cell.prototype._auto_highlight = function (modes) {
279 290 //Here we handle manually selected modes
280 291 if( this.user_highlight != undefined && this.user_highlight != 'auto' )
281 292 {
282 293 var mode = this.user_highlight;
283 294 CodeMirror.autoLoadMode(this.code_mirror, mode);
284 295 this.code_mirror.setOption('mode', mode);
285 296 return;
286 297 }
287 298 var first_line = this.code_mirror.getLine(0);
288 299 // loop on every pairs
289 300 for( var mode in modes) {
290 301 var regs = modes[mode]['reg'];
291 302 // only one key every time but regexp can't be keys...
292 303 for(var reg in regs ) {
293 304 // here we handle non magic_modes
294 305 if(first_line.match(regs[reg]) != null) {
295 306 if (mode.search('magic_') != 0) {
296 this.code_mirror.setOption('mode',mode);
307 this.code_mirror.setOption('mode', mode);
297 308 CodeMirror.autoLoadMode(this.code_mirror, mode);
298 309 return;
299 310 }
300 311 var open = modes[mode]['open']|| "%%";
301 312 var close = modes[mode]['close']|| "%%end";
302 313 var mmode = mode;
303 314 mode = mmode.substr(6);
304 315 CodeMirror.autoLoadMode(this.code_mirror, mode);
305 316 // create on the fly a mode that swhitch between
306 317 // plain/text and smth else otherwise `%%` is
307 318 // source of some highlight issues.
308 319 // we use patchedGetMode to circumvent a bug in CM
309 320 CodeMirror.defineMode(mmode , function(config) {
310 321 return CodeMirror.multiplexingMode(
311 322 CodeMirror.patchedGetMode(config, 'text/plain'),
312 323 // always set someting on close
313 324 {open: open, close: close,
314 325 mode: CodeMirror.patchedGetMode(config, mode),
315 326 delimStyle: "delimit"
316 327 }
317 328 );
318 329 });
319 330 this.code_mirror.setOption('mode', mmode);
320 331 return;
321 332 }
322 333 }
323 334 }
324 335 // fallback on default (python)
325 336 var default_mode = this.default_mode || 'text/plain';
326 337 this.code_mirror.setOption('mode', default_mode);
327 338 };
328 339
329 340 IPython.Cell = Cell;
330 341
331 342 return IPython;
332 343
333 344 }(IPython));
334 345
@@ -1,428 +1,425 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // CodeCell
10 10 //============================================================================
11 11 /**
12 12 * An extendable module that provide base functionnality to create cell for notebook.
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule CodeCell
16 16 */
17 17
18 18
19 19 /* local util for codemirror */
20 20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}
21 21
22 22 /**
23 23 *
24 24 * function to delete until previous non blanking space character
25 25 * or first multiple of 4 tabstop.
26 26 * @private
27 27 */
28 28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 30 if (!posEq(from, to)) {cm.replaceRange("", from, to); return}
31 31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 32 var tabsize = cm.getOption('tabSize');
33 33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 34 var from = {ch:cur.ch-chToPrevTabStop,line:cur.line}
35 35 var select = cm.getRange(from,cur)
36 36 if( select.match(/^\ +$/) != null){
37 37 cm.replaceRange("",from,cur)
38 38 } else {
39 39 cm.deleteH(-1,"char")
40 40 }
41 41 };
42 42
43 43
44 44 var IPython = (function (IPython) {
45 45 "use strict";
46 46
47 47 var utils = IPython.utils;
48 48 var key = IPython.utils.keycodes;
49 49 CodeMirror.modeURL = "/static/codemirror/mode/%N/%N.js";
50 50
51 51 /**
52 52 * A Cell conceived to write code.
53 53 *
54 54 * The kernel doesn't have to be set at creation time, in that case
55 55 * it will be null and set_kernel has to be called later.
56 56 * @class CodeCell
57 57 * @extends IPython.Cell
58 58 *
59 59 * @constructor
60 60 * @param {Object|null} kernel
61 61 * @param {object|undefined} [options]
62 62 * @param [options.cm_config] {object} config to pass to CodeMirror
63 63 */
64 64 var CodeCell = function (kernel, options) {
65 var options = options || {}
66 65 this.kernel = kernel || null;
67 66 this.code_mirror = null;
68 67 this.input_prompt_number = null;
69 68 this.collapsed = false;
70 69 this.default_mode = 'python';
71 70
72 71
73 72 var cm_overwrite_options = {
74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess",'Backspace':"delSpaceToPrevTabStop"},
75 73 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
76 74 };
77 75
78 var arg_cm_options = options.cm_options || {};
79 var cm_config = $.extend({},CodeCell.cm_default, arg_cm_options, cm_overwrite_options);
80
81 var options = {};
82 options.cm_config = cm_config;
76 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
83 77
84 78 IPython.Cell.apply(this,[options]);
85 79
86 80 var that = this;
87 81 this.element.focusout(
88 82 function() { that.auto_highlight(); }
89 83 );
90 84 };
91 85
92 CodeCell.cm_default = {
86 CodeCell.options_default = {
87 cm_config : {
88 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess",'Backspace':"delSpaceToPrevTabStop"},
93 89 mode: 'python',
94 90 theme: 'ipython',
95 91 matchBrackets: true
92 }
96 93 };
97 94
98 95
99 96 CodeCell.prototype = new IPython.Cell();
100 97
101 98 /**
102 99 * @method auto_highlight
103 100 */
104 101 CodeCell.prototype.auto_highlight = function () {
105 102 this._auto_highlight(IPython.config.cell_magic_highlight)
106 103 };
107 104
108 105 /** @method create_element */
109 106 CodeCell.prototype.create_element = function () {
110 107 IPython.Cell.prototype.create_element.apply(this, arguments);
111 108
112 109 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
113 110 cell.attr('tabindex','2');
114 111
115 112 this.celltoolbar = new IPython.CellToolbar(this);
116 113
117 114 var input = $('<div></div>').addClass('input hbox');
118 115 var vbox = $('<div/>').addClass('vbox box-flex1')
119 116 input.append($('<div/>').addClass('prompt input_prompt'));
120 117 vbox.append(this.celltoolbar.element);
121 118 var input_area = $('<div/>').addClass('input_area');
122 119 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
123 120 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
124 121 vbox.append(input_area);
125 122 input.append(vbox);
126 123 var output = $('<div></div>');
127 124 cell.append(input).append(output);
128 125 this.element = cell;
129 126 this.output_area = new IPython.OutputArea(output, true);
130 127
131 128 // construct a completer only if class exist
132 129 // otherwise no print view
133 130 if (IPython.Completer !== undefined)
134 131 {
135 132 this.completer = new IPython.Completer(this);
136 133 }
137 134 };
138 135
139 136 /**
140 137 * This method gets called in CodeMirror's onKeyDown/onKeyPress
141 138 * handlers and is used to provide custom key handling. Its return
142 139 * value is used to determine if CodeMirror should ignore the event:
143 140 * true = ignore, false = don't ignore.
144 141 * @method handle_codemirror_keyevent
145 142 */
146 143 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
147 144
148 145 if (this.read_only){
149 146 return false;
150 147 }
151 148
152 149 var that = this;
153 150 // whatever key is pressed, first, cancel the tooltip request before
154 151 // they are sent, and remove tooltip if any, except for tab again
155 152 if (event.type === 'keydown' && event.which != key.TAB ) {
156 153 IPython.tooltip.remove_and_cancel_tooltip();
157 154 };
158 155
159 156 var cur = editor.getCursor();
160 157 if (event.keyCode === key.ENTER){
161 158 this.auto_highlight();
162 159 }
163 160
164 161 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
165 162 // Always ignore shift-enter in CodeMirror as we handle it.
166 163 return true;
167 164 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
168 165 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
169 166 // browser and keyboard layout !
170 167 // Pressing '(' , request tooltip, don't forget to reappend it
171 168 IPython.tooltip.pending(that);
172 169 } else if (event.which === key.UPARROW && event.type === 'keydown') {
173 170 // If we are not at the top, let CM handle the up arrow and
174 171 // prevent the global keydown handler from handling it.
175 172 if (!that.at_top()) {
176 173 event.stop();
177 174 return false;
178 175 } else {
179 176 return true;
180 177 };
181 178 } else if (event.which === key.ESC) {
182 179 IPython.tooltip.remove_and_cancel_tooltip(true);
183 180 return true;
184 181 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
185 182 // If we are not at the bottom, let CM handle the down arrow and
186 183 // prevent the global keydown handler from handling it.
187 184 if (!that.at_bottom()) {
188 185 event.stop();
189 186 return false;
190 187 } else {
191 188 return true;
192 189 };
193 190 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
194 191 if (editor.somethingSelected()){
195 192 var anchor = editor.getCursor("anchor");
196 193 var head = editor.getCursor("head");
197 194 if( anchor.line != head.line){
198 195 return false;
199 196 }
200 197 }
201 198 IPython.tooltip.request(that);
202 199 event.stop();
203 200 return true;
204 201 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
205 202 // Tab completion.
206 203 //Do not trim here because of tooltip
207 204 if (editor.somethingSelected()){return false}
208 205 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
209 206 if (pre_cursor.trim() === "") {
210 207 // Don't autocomplete if the part of the line before the cursor
211 208 // is empty. In this case, let CodeMirror handle indentation.
212 209 return false;
213 210 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && IPython.config.tooltip_on_tab ) {
214 211 IPython.tooltip.request(that);
215 212 // Prevent the event from bubbling up.
216 213 event.stop();
217 214 // Prevent CodeMirror from handling the tab.
218 215 return true;
219 216 } else {
220 217 event.stop();
221 218 this.completer.startCompletion();
222 219 return true;
223 220 };
224 221 } else {
225 222 // keypress/keyup also trigger on TAB press, and we don't want to
226 223 // use those to disable tab completion.
227 224 return false;
228 225 };
229 226 return false;
230 227 };
231 228
232 229
233 230 // Kernel related calls.
234 231
235 232 CodeCell.prototype.set_kernel = function (kernel) {
236 233 this.kernel = kernel;
237 234 }
238 235
239 236 /**
240 237 * Execute current code cell to the kernel
241 238 * @method execute
242 239 */
243 240 CodeCell.prototype.execute = function () {
244 241 this.output_area.clear_output(true, true, true);
245 242 this.set_input_prompt('*');
246 243 this.element.addClass("running");
247 244 var callbacks = {
248 245 'execute_reply': $.proxy(this._handle_execute_reply, this),
249 246 'output': $.proxy(this.output_area.handle_output, this.output_area),
250 247 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
251 248 'set_next_input': $.proxy(this._handle_set_next_input, this)
252 249 };
253 250 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
254 251 };
255 252
256 253 /**
257 254 * @method _handle_execute_reply
258 255 * @private
259 256 */
260 257 CodeCell.prototype._handle_execute_reply = function (content) {
261 258 this.set_input_prompt(content.execution_count);
262 259 this.element.removeClass("running");
263 260 $([IPython.events]).trigger('set_dirty.Notebook', {'value': true});
264 261 }
265 262
266 263 CodeCell.prototype._handle_set_next_input = function (text) {
267 264 var data = {'cell': this, 'text': text}
268 265 $([IPython.events]).trigger('set_next_input.Notebook', data);
269 266 }
270 267
271 268 // Basic cell manipulation.
272 269
273 270 CodeCell.prototype.select = function () {
274 271 IPython.Cell.prototype.select.apply(this);
275 272 this.code_mirror.refresh();
276 273 this.code_mirror.focus();
277 274 this.auto_highlight();
278 275 // We used to need an additional refresh() after the focus, but
279 276 // it appears that this has been fixed in CM. This bug would show
280 277 // up on FF when a newly loaded markdown cell was edited.
281 278 };
282 279
283 280
284 281 CodeCell.prototype.select_all = function () {
285 282 var start = {line: 0, ch: 0};
286 283 var nlines = this.code_mirror.lineCount();
287 284 var last_line = this.code_mirror.getLine(nlines-1);
288 285 var end = {line: nlines-1, ch: last_line.length};
289 286 this.code_mirror.setSelection(start, end);
290 287 };
291 288
292 289
293 290 CodeCell.prototype.collapse = function () {
294 291 this.collapsed = true;
295 292 this.output_area.collapse();
296 293 };
297 294
298 295
299 296 CodeCell.prototype.expand = function () {
300 297 this.collapsed = false;
301 298 this.output_area.expand();
302 299 };
303 300
304 301
305 302 CodeCell.prototype.toggle_output = function () {
306 303 this.collapsed = Boolean(1 - this.collapsed);
307 304 this.output_area.toggle_output();
308 305 };
309 306
310 307
311 308 CodeCell.prototype.toggle_output_scroll = function () {
312 309 this.output_area.toggle_scroll();
313 310 };
314 311
315 312
316 313 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
317 314 var ns = prompt_value || "&nbsp;";
318 315 return 'In&nbsp;[' + ns + ']:'
319 316 };
320 317
321 318 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
322 319 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
323 320 for(var i=1; i < lines_number; i++){html.push(['...:'])};
324 321 return html.join('</br>')
325 322 };
326 323
327 324 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
328 325
329 326
330 327 CodeCell.prototype.set_input_prompt = function (number) {
331 328 var nline = 1
332 329 if( this.code_mirror != undefined) {
333 330 nline = this.code_mirror.lineCount();
334 331 }
335 332 this.input_prompt_number = number;
336 333 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
337 334 this.element.find('div.input_prompt').html(prompt_html);
338 335 };
339 336
340 337
341 338 CodeCell.prototype.clear_input = function () {
342 339 this.code_mirror.setValue('');
343 340 };
344 341
345 342
346 343 CodeCell.prototype.get_text = function () {
347 344 return this.code_mirror.getValue();
348 345 };
349 346
350 347
351 348 CodeCell.prototype.set_text = function (code) {
352 349 return this.code_mirror.setValue(code);
353 350 };
354 351
355 352
356 353 CodeCell.prototype.at_top = function () {
357 354 var cursor = this.code_mirror.getCursor();
358 355 if (cursor.line === 0 && cursor.ch === 0) {
359 356 return true;
360 357 } else {
361 358 return false;
362 359 }
363 360 };
364 361
365 362
366 363 CodeCell.prototype.at_bottom = function () {
367 364 var cursor = this.code_mirror.getCursor();
368 365 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
369 366 return true;
370 367 } else {
371 368 return false;
372 369 }
373 370 };
374 371
375 372
376 373 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
377 374 this.output_area.clear_output(stdout, stderr, other);
378 375 };
379 376
380 377
381 378 // JSON serialization
382 379
383 380 CodeCell.prototype.fromJSON = function (data) {
384 381 IPython.Cell.prototype.fromJSON.apply(this, arguments);
385 382 if (data.cell_type === 'code') {
386 383 if (data.input !== undefined) {
387 384 this.set_text(data.input);
388 385 // make this value the starting point, so that we can only undo
389 386 // to this state, instead of a blank cell
390 387 this.code_mirror.clearHistory();
391 388 this.auto_highlight();
392 389 }
393 390 if (data.prompt_number !== undefined) {
394 391 this.set_input_prompt(data.prompt_number);
395 392 } else {
396 393 this.set_input_prompt();
397 394 };
398 395 this.output_area.fromJSON(data.outputs);
399 396 if (data.collapsed !== undefined) {
400 397 if (data.collapsed) {
401 398 this.collapse();
402 399 } else {
403 400 this.expand();
404 401 };
405 402 };
406 403 };
407 404 };
408 405
409 406
410 407 CodeCell.prototype.toJSON = function () {
411 408 var data = IPython.Cell.prototype.toJSON.apply(this);
412 409 data.input = this.get_text();
413 410 data.cell_type = 'code';
414 411 if (this.input_prompt_number) {
415 412 data.prompt_number = this.input_prompt_number;
416 413 };
417 414 var outputs = this.output_area.toJSON();
418 415 data.outputs = outputs;
419 416 data.language = 'python';
420 417 data.collapsed = this.collapsed;
421 418 return data;
422 419 };
423 420
424 421
425 422 IPython.CodeCell = CodeCell;
426 423
427 424 return IPython;
428 425 }(IPython));
@@ -1,561 +1,598 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // TextCell
10 10 //============================================================================
11 11
12
13
12 14 /**
13 15 A module that allow to create different type of Text Cell
14 16 @module IPython
15 17 @namespace IPython
16 18 */
17 19 var IPython = (function (IPython) {
18 20
19 21 // TextCell base class
20 22 var key = IPython.utils.keycodes;
21 23
22 24 /**
23 25 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
24 26 * cell start as not redered.
25 27 *
26 28 * @class TextCell
27 29 * @constructor TextCell
28 30 * @extend Ipython.Cell
29 31 * @param {object|undefined} [options]
30 32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
33 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
31 34 */
32 35 var TextCell = function (options) {
33 this.code_mirror_mode = this.code_mirror_mode || 'htmlmixed';
34 var options = options || {};
36 // in all TextCell/Cell subclasses
37 // do not assign most of members here, just pass it down
38 // in the options dict potentially overwriting what you wish.
39 // they will be assigned in the base class.
35 40
41 // we cannot put this as a class key as it has handle to "this".
36 42 var cm_overwrite_options = {
37 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
38 43 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
39 44 };
40 45
41 var arg_cm_options = options.cm_options || {};
42 var cm_config = $.extend({},TextCell.cm_default, arg_cm_options, cm_overwrite_options);
46 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
43 47
44 var options = {};
45 options.cm_config = cm_config;
48 IPython.Cell.apply(this, [options]);
46 49
47 50
48 IPython.Cell.apply(this, [options]);
49 51 this.rendered = false;
50 52 this.cell_type = this.cell_type || 'text';
51 53 };
52 54
53 TextCell.cm_default = {
54 mode: this.code_mirror_mode,
55 theme: 'default',
56 value: this.placeholder,
55 TextCell.prototype = new IPython.Cell();
56
57 TextCell.options_default = {
58 cm_config : {
59 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 mode: 'htmlmixed',
57 61 lineWrapping : true,
58 62 }
63 };
59 64
60 65
61 TextCell.prototype = new IPython.Cell();
62 66
63 67 /**
64 68 * Create the DOM element of the TextCell
65 69 * @method create_element
66 70 * @private
67 71 */
68 72 TextCell.prototype.create_element = function () {
69 73 IPython.Cell.prototype.create_element.apply(this, arguments);
70 74 var cell = $("<div>").addClass('cell text_cell border-box-sizing vbox');
71 75 cell.attr('tabindex','2');
72 76
73 77 this.celltoolbar = new IPython.CellToolbar(this);
74 78 cell.append(this.celltoolbar.element);
75 79
76 80 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
77 81 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
82
78 83 // The tabindex=-1 makes this div focusable.
79 84 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
80 85 addClass('rendered_html').attr('tabindex','-1');
81 86 cell.append(input_area).append(render_area);
82 87 this.element = cell;
83 88 };
84 89
85 90
86 91 /**
87 92 * Bind the DOM evet to cell actions
88 93 * Need to be called after TextCell.create_element
89 94 * @private
90 95 * @method bind_event
91 96 */
92 97 TextCell.prototype.bind_events = function () {
93 98 IPython.Cell.prototype.bind_events.apply(this);
94 99 var that = this;
95 100 this.element.keydown(function (event) {
96 101 if (event.which === 13 && !event.shiftKey) {
97 102 if (that.rendered) {
98 103 that.edit();
99 104 return false;
100 105 };
101 106 };
102 107 });
103 108 this.element.dblclick(function () {
104 109 that.edit();
105 110 });
106 111 };
107 112
108 113 /**
109 114 * This method gets called in CodeMirror's onKeyDown/onKeyPress
110 115 * handlers and is used to provide custom key handling.
111 116 *
112 117 * Subclass should override this method to have custom handeling
113 118 *
114 119 * @method handle_codemirror_keyevent
115 120 * @param {CodeMirror} editor - The codemirror instance bound to the cell
116 121 * @param {event} event -
117 122 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
118 123 */
119 124 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
120 125
121 126 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
122 127 // Always ignore shift-enter in CodeMirror as we handle it.
123 128 return true;
124 129 }
125 130 return false;
126 131 };
127 132
128 133 /**
129 134 * Select the current cell and trigger 'focus'
130 135 * @method select
131 136 */
132 137 TextCell.prototype.select = function () {
133 138 IPython.Cell.prototype.select.apply(this);
134 139 var output = this.element.find("div.text_cell_render");
135 140 output.trigger('focus');
136 141 };
137 142
138 143 /**
139 144 * unselect the current cell and `render` it
140 145 * @method unselect
141 146 */
142 147 TextCell.prototype.unselect = function() {
143 148 // render on selection of another cell
144 149 this.render();
145 150 IPython.Cell.prototype.unselect.apply(this);
146 151 };
147 152
148 153 /**
149 154 *
150 155 * put the current cell in edition mode
151 156 * @method edit
152 157 */
153 158 TextCell.prototype.edit = function () {
154 159 if ( this.read_only ) return;
155 160 if (this.rendered === true) {
156 161 var text_cell = this.element;
157 162 var output = text_cell.find("div.text_cell_render");
158 163 output.hide();
159 164 text_cell.find('div.text_cell_input').show();
160 165 this.code_mirror.refresh();
161 166 this.code_mirror.focus();
162 167 // We used to need an additional refresh() after the focus, but
163 168 // it appears that this has been fixed in CM. This bug would show
164 169 // up on FF when a newly loaded markdown cell was edited.
165 170 this.rendered = false;
166 171 if (this.get_text() === this.placeholder) {
167 172 this.set_text('');
168 173 this.refresh();
169 174 }
170 175 }
171 176 };
172 177
173 178
174 179 /**
175 180 * Empty, Subclasses must define render.
176 181 * @method render
177 182 */
178 183 TextCell.prototype.render = function () {};
179 184
180 185
181 186 /**
182 187 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
183 188 * @method get_text
184 189 * @retrun {string} CodeMirror current text value
185 190 */
186 191 TextCell.prototype.get_text = function() {
187 192 return this.code_mirror.getValue();
188 193 };
189 194
190 195 /**
191 196 * @param {string} text - Codemiror text value
192 197 * @see TextCell#get_text
193 198 * @method set_text
194 199 * */
195 200 TextCell.prototype.set_text = function(text) {
196 201 this.code_mirror.setValue(text);
197 202 this.code_mirror.refresh();
198 203 };
199 204
200 205 /**
201 206 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
202 207 * @method get_rendered
203 208 * @return {html} html of rendered element
204 209 * */
205 210 TextCell.prototype.get_rendered = function() {
206 211 return this.element.find('div.text_cell_render').html();
207 212 };
208 213
209 214 /**
210 215 * @method set_rendered
211 216 */
212 217 TextCell.prototype.set_rendered = function(text) {
213 218 this.element.find('div.text_cell_render').html(text);
214 219 };
215 220
216 221 /**
217 222 * not deprecated, but implementation wrong
218 223 * @method at_top
219 224 * @deprecated
220 225 * @return {Boolean} true is cell rendered, false otherwise
221 226 * I doubt this is what it is supposed to do
222 227 * this implementation is completly false
223 228 */
224 229 TextCell.prototype.at_top = function () {
225 230 if (this.rendered) {
226 231 return true;
227 232 } else {
228 233 return false;
229 234 }
230 235 };
231 236
232 237
233 238 /**
234 239 * not deprecated, but implementation wrong
235 240 * @method at_bottom
236 241 * @deprecated
237 242 * @return {Boolean} true is cell rendered, false otherwise
238 243 * I doubt this is what it is supposed to do
239 244 * this implementation is completly false
240 245 * */
241 246 TextCell.prototype.at_bottom = function () {
242 247 if (this.rendered) {
243 248 return true;
244 249 } else {
245 250 return false;
246 251 }
247 252 };
248 253
249 254 /**
250 255 * Create Text cell from JSON
251 256 * @param {json} data - JSON serialized text-cell
252 257 * @method fromJSON
253 258 */
254 259 TextCell.prototype.fromJSON = function (data) {
255 260 IPython.Cell.prototype.fromJSON.apply(this, arguments);
256 261 if (data.cell_type === this.cell_type) {
257 262 if (data.source !== undefined) {
258 263 this.set_text(data.source);
259 264 // make this value the starting point, so that we can only undo
260 265 // to this state, instead of a blank cell
261 266 this.code_mirror.clearHistory();
262 267 this.set_rendered(data.rendered || '');
263 268 this.rendered = false;
264 269 this.render();
265 270 }
266 271 }
267 272 };
268 273
269 274 /** Generate JSON from cell
270 275 * @return {object} cell data serialised to json
271 276 */
272 277 TextCell.prototype.toJSON = function () {
273 278 var data = IPython.Cell.prototype.toJSON.apply(this);
274 279 data.cell_type = this.cell_type;
275 280 data.source = this.get_text();
276 281 return data;
277 282 };
278 283
279 284
280 285 /**
281 286 * @constructor HtmlCell
282 287 * @class HtmlCell
283 288 * @extends Ipython.TextCell
284 289 */
285 var HTMLCell = function () {
286 this.placeholder = "Type <strong>HTML</strong> and LaTeX: $\\alpha^2$";
287 IPython.TextCell.apply(this, arguments);
290 var HTMLCell = function (options) {
291
292 options = this.mergeopt(HTMLCell,options);
293 TextCell.apply(this, [options]);
294
288 295 this.cell_type = 'html';
289 296 };
290 297
298 HTMLCell.options_default = {
299 cm_config : {
300 mode: 'htmlmixed',
301 },
302 placeholder: "Type <strong>HTML</strong> and LaTeX: $\\alpha^2$"
303 };
304
291 305
292 306 HTMLCell.prototype = new TextCell();
293 307
294 308 /**
295 309 * @method render
296 310 */
297 311 HTMLCell.prototype.render = function () {
298 312 if (this.rendered === false) {
299 313 var text = this.get_text();
300 314 if (text === "") { text = this.placeholder; }
301 315 this.set_rendered(text);
302 316 this.typeset();
303 317 this.element.find('div.text_cell_input').hide();
304 318 this.element.find("div.text_cell_render").show();
305 319 this.rendered = true;
306 320 }
307 321 };
308 322
309 323
310 324 /**
311 325 * @class MarkdownCell
312 326 * @constructor MarkdownCell
313 327 * @extends Ipython.HtmlCell
314 328 */
315 var MarkdownCell = function () {
316 this.placeholder = "Type *Markdown* and LaTeX: $\\alpha^2$";
317 IPython.TextCell.apply(this, arguments);
329 var MarkdownCell = function (options) {
330 var options = options || {};
331
332 options = this.mergeopt(MarkdownCell,options);
333 TextCell.apply(this, [options]);
334
318 335 this.cell_type = 'markdown';
319 336 };
320 337
338 MarkdownCell.options_default = {
339 cm_config: {
340 mode: 'markdown'
341 },
342 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
343 }
344
345
346
321 347
322 348 MarkdownCell.prototype = new TextCell();
323 349
324 350 /**
325 351 * @method render
326 352 */
327 353 MarkdownCell.prototype.render = function () {
328 354 if (this.rendered === false) {
329 355 var text = this.get_text();
330 356 if (text === "") { text = this.placeholder; }
331 357 text = IPython.mathjaxutils.remove_math(text)
332 358 var html = IPython.markdown_converter.makeHtml(text);
333 359 html = IPython.mathjaxutils.replace_math(html)
334 360 try {
335 361 this.set_rendered(html);
336 362 } catch (e) {
337 363 console.log("Error running Javascript in Markdown:");
338 364 console.log(e);
339 365 this.set_rendered($("<div/>").addClass("js-error").html(
340 366 "Error rendering Markdown!<br/>" + e.toString())
341 367 );
342 368 }
343 369 this.element.find('div.text_cell_input').hide();
344 370 this.element.find("div.text_cell_render").show();
345 371 var code_snippets = this.element.find("pre > code");
346 372 code_snippets.replaceWith(function () {
347 373 var code = $(this).html();
348 374 /* Substitute br for newlines and &nbsp; for spaces
349 375 before highlighting, since prettify doesn't
350 376 preserve those on all browsers */
351 377 code = code.replace(/(\r\n|\n|\r)/gm, "<br/>");
352 378 code = code.replace(/ /gm, '&nbsp;');
353 379 code = prettyPrintOne(code);
354 380
355 381 return '<code class="prettyprint">' + code + '</code>';
356 382 });
357 383 this.typeset()
358 384 this.rendered = true;
359 385 }
360 386 };
361 387
362 388
363 389 // RawCell
364 390
365 391 /**
366 392 * @class RawCell
367 393 * @constructor RawCell
368 394 * @extends Ipython.TextCell
369 395 */
370 var RawCell = function () {
371 this.placeholder = "Type plain text and LaTeX: $\\alpha^2$";
372 this.code_mirror_mode = 'rst';
373 IPython.TextCell.apply(this, arguments);
396 var RawCell = function (options) {
397
398 options = this.mergeopt(RawCell,options)
399 TextCell.apply(this, [options]);
400
374 401 this.cell_type = 'raw';
375 var that = this
376 402
403 var that = this
377 404 this.element.focusout(
378 405 function() { that.auto_highlight(); }
379 406 );
380 407 };
381 408
409 RawCell.options_default = {
410 placeholder : "Type plain text and LaTeX: $\\alpha^2$"
411 };
412
413
382 414
383 415 RawCell.prototype = new TextCell();
384 416
385 417 /**
386 418 * Trigger autodetection of highlight scheme for current cell
387 419 * @method auto_highlight
388 420 */
389 421 RawCell.prototype.auto_highlight = function () {
390 422 this._auto_highlight(IPython.config.raw_cell_highlight);
391 423 };
392 424
393 425 /** @method render **/
394 426 RawCell.prototype.render = function () {
395 427 this.rendered = true;
396 428 this.edit();
397 429 };
398 430
399 431
400 432 /** @method handle_codemirror_keyevent **/
401 433 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
402 434
403 435 var that = this;
404 436 if (event.which === key.UPARROW && event.type === 'keydown') {
405 437 // If we are not at the top, let CM handle the up arrow and
406 438 // prevent the global keydown handler from handling it.
407 439 if (!that.at_top()) {
408 440 event.stop();
409 441 return false;
410 442 } else {
411 443 return true;
412 444 };
413 445 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
414 446 // If we are not at the bottom, let CM handle the down arrow and
415 447 // prevent the global keydown handler from handling it.
416 448 if (!that.at_bottom()) {
417 449 event.stop();
418 450 return false;
419 451 } else {
420 452 return true;
421 453 };
422 454 };
423 455 return false;
424 456 };
425 457
426 458 /** @method select **/
427 459 RawCell.prototype.select = function () {
428 460 IPython.Cell.prototype.select.apply(this);
429 461 this.code_mirror.refresh();
430 462 this.code_mirror.focus();
431 463 };
432 464
433 465 /** @method at_top **/
434 466 RawCell.prototype.at_top = function () {
435 467 var cursor = this.code_mirror.getCursor();
436 468 if (cursor.line === 0 && cursor.ch === 0) {
437 469 return true;
438 470 } else {
439 471 return false;
440 472 }
441 473 };
442 474
443 475
444 476 /** @method at_bottom **/
445 477 RawCell.prototype.at_bottom = function () {
446 478 var cursor = this.code_mirror.getCursor();
447 479 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
448 480 return true;
449 481 } else {
450 482 return false;
451 483 }
452 484 };
453 485
454 486
455 487 /**
456 488 * @class HeadingCell
457 489 * @extends Ipython.TextCell
458 490 */
459 491
460 492 /**
461 493 * @constructor HeadingCell
462 494 * @extends Ipython.TextCell
463 495 */
464 var HeadingCell = function () {
465 this.placeholder = "Type Heading Here";
466 IPython.TextCell.apply(this, arguments);
496 var HeadingCell = function (options) {
497
498 options = this.mergeopt(HeadingCell,options)
499 TextCell.apply(this, [options]);
500
467 501 /**
468 502 * heading level of the cell, use getter and setter to access
469 503 * @property level
470 504 */
471 505 this.level = 1;
472 506 this.cell_type = 'heading';
473 507 };
474 508
509 HeadingCell.options_default = {
510 placeholder: "Type Heading Here"
511 };
475 512
476 513 HeadingCell.prototype = new TextCell();
477 514
478 515 /** @method fromJSON */
479 516 HeadingCell.prototype.fromJSON = function (data) {
480 517 if (data.level != undefined){
481 518 this.level = data.level;
482 519 }
483 IPython.TextCell.prototype.fromJSON.apply(this, arguments);
520 TextCell.prototype.fromJSON.apply(this, arguments);
484 521 };
485 522
486 523
487 524 /** @method toJSON */
488 525 HeadingCell.prototype.toJSON = function () {
489 var data = IPython.TextCell.prototype.toJSON.apply(this);
526 var data = TextCell.prototype.toJSON.apply(this);
490 527 data.level = this.get_level();
491 528 return data;
492 529 };
493 530
494 531
495 532 /**
496 533 * Change heading level of cell, and re-render
497 534 * @method set_level
498 535 */
499 536 HeadingCell.prototype.set_level = function (level) {
500 537 this.level = level;
501 538 if (this.rendered) {
502 539 this.rendered = false;
503 540 this.render();
504 541 };
505 542 };
506 543
507 544 /** The depth of header cell, based on html (h1 to h6)
508 545 * @method get_level
509 546 * @return {integer} level - for 1 to 6
510 547 */
511 548 HeadingCell.prototype.get_level = function () {
512 549 return this.level;
513 550 };
514 551
515 552
516 553 HeadingCell.prototype.set_rendered = function (text) {
517 554 var r = this.element.find("div.text_cell_render");
518 555 r.empty();
519 556 var link = text.replace(/ /g, '_');
520 557 r.append(
521 558 $('<h'+this.level+'/>')
522 559 .append(
523 560 $('<a/>')
524 561 .addClass('heading-anchor')
525 562 .attr('href', '#' + link)
526 563 .attr('id', link)
527 564 .html(text)
528 565 )
529 566 );
530 567 };
531 568
532 569
533 570 HeadingCell.prototype.get_rendered = function () {
534 571 var r = this.element.find("div.text_cell_render");
535 572 return r.children().first().html();
536 573 };
537 574
538 575
539 576 HeadingCell.prototype.render = function () {
540 577 if (this.rendered === false) {
541 578 var text = this.get_text();
542 579 if (text === "") { text = this.placeholder; }
543 580 this.set_rendered(text);
544 581 this.typeset();
545 582 this.element.find('div.text_cell_input').hide();
546 583 this.element.find("div.text_cell_render").show();
547 584 this.rendered = true;
548 585 };
549 586 };
550 587
551 588 IPython.TextCell = TextCell;
552 589 IPython.HTMLCell = HTMLCell;
553 590 IPython.MarkdownCell = MarkdownCell;
554 591 IPython.RawCell = RawCell;
555 592 IPython.HeadingCell = HeadingCell;
556 593
557 594
558 595 return IPython;
559 596
560 597 }(IPython));
561 598
General Comments 0
You need to be logged in to leave comments. Login now