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