##// END OF EJS Templates
Merge pull request #2894 from Carreau/cm-configurable...
Bussonnier Matthias -
r9695:98972ec3 merge
parent child Browse files
Show More
@@ -1,314 +1,327
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 * force codemirror highlight mode
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,412 +1,427
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 * @param {object|undefined} [options]
62 * @param [options.cm_config] {object} config to pass to CodeMirror
61 63 */
62 var CodeCell = function (kernel) {
64 var CodeCell = function (kernel, options) {
65 var options = options || {}
63 66 this.kernel = kernel || null;
64 67 this.code_mirror = null;
65 68 this.input_prompt_number = null;
66 69 this.collapsed = false;
67 70 this.default_mode = 'python';
68 IPython.Cell.apply(this, arguments);
71
72
73 var cm_overwrite_options = {
74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess",'Backspace':"delSpaceToPrevTabStop"},
75 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
76 };
77
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;
83
84 IPython.Cell.apply(this,[options]);
69 85
70 86 var that = this;
71 87 this.element.focusout(
72 88 function() { that.auto_highlight(); }
73 89 );
74 90 };
75 91
92 CodeCell.cm_default = {
93 mode: 'python',
94 theme: 'ipython',
95 matchBrackets: true
96 };
97
98
76 99 CodeCell.prototype = new IPython.Cell();
77 100
78 101 /**
79 102 * @method auto_highlight
80 103 */
81 104 CodeCell.prototype.auto_highlight = function () {
82 105 this._auto_highlight(IPython.config.cell_magic_highlight)
83 106 };
84 107
85 108 /** @method create_element */
86 109 CodeCell.prototype.create_element = function () {
87 110 IPython.Cell.prototype.create_element.apply(this, arguments);
88 111
89 112 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
90 113 cell.attr('tabindex','2');
91 114
92 115 this.celltoolbar = new IPython.CellToolbar(this);
93 116
94 117 var input = $('<div></div>').addClass('input hbox');
95 118 var vbox = $('<div/>').addClass('vbox box-flex1')
96 119 input.append($('<div/>').addClass('prompt input_prompt'));
97 120 vbox.append(this.celltoolbar.element);
98 121 var input_area = $('<div/>').addClass('input_area');
99 this.code_mirror = CodeMirror(input_area.get(0), {
100 indentUnit : 4,
101 mode: 'python',
102 theme: 'ipython',
103 readOnly: this.read_only,
104 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess",'Backspace':"delSpaceToPrevTabStop"},
105 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this),
106 matchBrackets: true
107 });
122 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
108 123 vbox.append(input_area);
109 124 input.append(vbox);
110 125 var output = $('<div></div>');
111 126 cell.append(input).append(output);
112 127 this.element = cell;
113 128 this.output_area = new IPython.OutputArea(output, true);
114 129
115 130 // construct a completer only if class exist
116 131 // otherwise no print view
117 132 if (IPython.Completer !== undefined)
118 133 {
119 134 this.completer = new IPython.Completer(this);
120 135 }
121 136 };
122 137
123 138 /**
124 139 * This method gets called in CodeMirror's onKeyDown/onKeyPress
125 140 * handlers and is used to provide custom key handling. Its return
126 141 * value is used to determine if CodeMirror should ignore the event:
127 142 * true = ignore, false = don't ignore.
128 143 * @method handle_codemirror_keyevent
129 144 */
130 145 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
131 146
132 147 if (this.read_only){
133 148 return false;
134 149 }
135 150
136 151 var that = this;
137 152 // whatever key is pressed, first, cancel the tooltip request before
138 153 // they are sent, and remove tooltip if any, except for tab again
139 154 if (event.type === 'keydown' && event.which != key.TAB ) {
140 155 IPython.tooltip.remove_and_cancel_tooltip();
141 156 };
142 157
143 158 var cur = editor.getCursor();
144 159 if (event.keyCode === key.ENTER){
145 160 this.auto_highlight();
146 161 }
147 162
148 163 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
149 164 // Always ignore shift-enter in CodeMirror as we handle it.
150 165 return true;
151 166 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
152 167 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
153 168 // browser and keyboard layout !
154 169 // Pressing '(' , request tooltip, don't forget to reappend it
155 170 IPython.tooltip.pending(that);
156 171 } else if (event.which === key.UPARROW && event.type === 'keydown') {
157 172 // If we are not at the top, let CM handle the up arrow and
158 173 // prevent the global keydown handler from handling it.
159 174 if (!that.at_top()) {
160 175 event.stop();
161 176 return false;
162 177 } else {
163 178 return true;
164 179 };
165 180 } else if (event.which === key.ESC) {
166 181 IPython.tooltip.remove_and_cancel_tooltip(true);
167 182 return true;
168 183 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
169 184 // If we are not at the bottom, let CM handle the down arrow and
170 185 // prevent the global keydown handler from handling it.
171 186 if (!that.at_bottom()) {
172 187 event.stop();
173 188 return false;
174 189 } else {
175 190 return true;
176 191 };
177 192 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
178 193 if (editor.somethingSelected()){
179 194 var anchor = editor.getCursor("anchor");
180 195 var head = editor.getCursor("head");
181 196 if( anchor.line != head.line){
182 197 return false;
183 198 }
184 199 }
185 200 IPython.tooltip.request(that);
186 201 event.stop();
187 202 return true;
188 203 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
189 204 // Tab completion.
190 205 //Do not trim here because of tooltip
191 206 if (editor.somethingSelected()){return false}
192 207 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
193 208 if (pre_cursor.trim() === "") {
194 209 // Don't autocomplete if the part of the line before the cursor
195 210 // is empty. In this case, let CodeMirror handle indentation.
196 211 return false;
197 212 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && IPython.config.tooltip_on_tab ) {
198 213 IPython.tooltip.request(that);
199 214 // Prevent the event from bubbling up.
200 215 event.stop();
201 216 // Prevent CodeMirror from handling the tab.
202 217 return true;
203 218 } else {
204 219 event.stop();
205 220 this.completer.startCompletion();
206 221 return true;
207 222 };
208 223 } else {
209 224 // keypress/keyup also trigger on TAB press, and we don't want to
210 225 // use those to disable tab completion.
211 226 return false;
212 227 };
213 228 return false;
214 229 };
215 230
216 231
217 232 // Kernel related calls.
218 233
219 234 CodeCell.prototype.set_kernel = function (kernel) {
220 235 this.kernel = kernel;
221 236 }
222 237
223 238 /**
224 239 * Execute current code cell to the kernel
225 240 * @method execute
226 241 */
227 242 CodeCell.prototype.execute = function () {
228 243 this.output_area.clear_output(true, true, true);
229 244 this.set_input_prompt('*');
230 245 this.element.addClass("running");
231 246 var callbacks = {
232 247 'execute_reply': $.proxy(this._handle_execute_reply, this),
233 248 'output': $.proxy(this.output_area.handle_output, this.output_area),
234 249 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
235 250 'set_next_input': $.proxy(this._handle_set_next_input, this)
236 251 };
237 252 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
238 253 };
239 254
240 255 /**
241 256 * @method _handle_execute_reply
242 257 * @private
243 258 */
244 259 CodeCell.prototype._handle_execute_reply = function (content) {
245 260 this.set_input_prompt(content.execution_count);
246 261 this.element.removeClass("running");
247 262 $([IPython.events]).trigger('set_dirty.Notebook', {'value': true});
248 263 }
249 264
250 265 CodeCell.prototype._handle_set_next_input = function (text) {
251 266 var data = {'cell': this, 'text': text}
252 267 $([IPython.events]).trigger('set_next_input.Notebook', data);
253 268 }
254 269
255 270 // Basic cell manipulation.
256 271
257 272 CodeCell.prototype.select = function () {
258 273 IPython.Cell.prototype.select.apply(this);
259 274 this.code_mirror.refresh();
260 275 this.code_mirror.focus();
261 276 this.auto_highlight();
262 277 // We used to need an additional refresh() after the focus, but
263 278 // it appears that this has been fixed in CM. This bug would show
264 279 // up on FF when a newly loaded markdown cell was edited.
265 280 };
266 281
267 282
268 283 CodeCell.prototype.select_all = function () {
269 284 var start = {line: 0, ch: 0};
270 285 var nlines = this.code_mirror.lineCount();
271 286 var last_line = this.code_mirror.getLine(nlines-1);
272 287 var end = {line: nlines-1, ch: last_line.length};
273 288 this.code_mirror.setSelection(start, end);
274 289 };
275 290
276 291
277 292 CodeCell.prototype.collapse = function () {
278 293 this.collapsed = true;
279 294 this.output_area.collapse();
280 295 };
281 296
282 297
283 298 CodeCell.prototype.expand = function () {
284 299 this.collapsed = false;
285 300 this.output_area.expand();
286 301 };
287 302
288 303
289 304 CodeCell.prototype.toggle_output = function () {
290 305 this.collapsed = Boolean(1 - this.collapsed);
291 306 this.output_area.toggle_output();
292 307 };
293 308
294 309
295 310 CodeCell.prototype.toggle_output_scroll = function () {
296 311 this.output_area.toggle_scroll();
297 312 };
298 313
299 314
300 315 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
301 316 var ns = prompt_value || "&nbsp;";
302 317 return 'In&nbsp;[' + ns + ']:'
303 318 };
304 319
305 320 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
306 321 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
307 322 for(var i=1; i < lines_number; i++){html.push(['...:'])};
308 323 return html.join('</br>')
309 324 };
310 325
311 326 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
312 327
313 328
314 329 CodeCell.prototype.set_input_prompt = function (number) {
315 330 var nline = 1
316 331 if( this.code_mirror != undefined) {
317 332 nline = this.code_mirror.lineCount();
318 333 }
319 334 this.input_prompt_number = number;
320 335 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
321 336 this.element.find('div.input_prompt').html(prompt_html);
322 337 };
323 338
324 339
325 340 CodeCell.prototype.clear_input = function () {
326 341 this.code_mirror.setValue('');
327 342 };
328 343
329 344
330 345 CodeCell.prototype.get_text = function () {
331 346 return this.code_mirror.getValue();
332 347 };
333 348
334 349
335 350 CodeCell.prototype.set_text = function (code) {
336 351 return this.code_mirror.setValue(code);
337 352 };
338 353
339 354
340 355 CodeCell.prototype.at_top = function () {
341 356 var cursor = this.code_mirror.getCursor();
342 357 if (cursor.line === 0 && cursor.ch === 0) {
343 358 return true;
344 359 } else {
345 360 return false;
346 361 }
347 362 };
348 363
349 364
350 365 CodeCell.prototype.at_bottom = function () {
351 366 var cursor = this.code_mirror.getCursor();
352 367 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
353 368 return true;
354 369 } else {
355 370 return false;
356 371 }
357 372 };
358 373
359 374
360 375 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
361 376 this.output_area.clear_output(stdout, stderr, other);
362 377 };
363 378
364 379
365 380 // JSON serialization
366 381
367 382 CodeCell.prototype.fromJSON = function (data) {
368 383 IPython.Cell.prototype.fromJSON.apply(this, arguments);
369 384 if (data.cell_type === 'code') {
370 385 if (data.input !== undefined) {
371 386 this.set_text(data.input);
372 387 // make this value the starting point, so that we can only undo
373 388 // to this state, instead of a blank cell
374 389 this.code_mirror.clearHistory();
375 390 this.auto_highlight();
376 391 }
377 392 if (data.prompt_number !== undefined) {
378 393 this.set_input_prompt(data.prompt_number);
379 394 } else {
380 395 this.set_input_prompt();
381 396 };
382 397 this.output_area.fromJSON(data.outputs);
383 398 if (data.collapsed !== undefined) {
384 399 if (data.collapsed) {
385 400 this.collapse();
386 401 } else {
387 402 this.expand();
388 403 };
389 404 };
390 405 };
391 406 };
392 407
393 408
394 409 CodeCell.prototype.toJSON = function () {
395 410 var data = IPython.Cell.prototype.toJSON.apply(this);
396 411 data.input = this.get_text();
397 412 data.cell_type = 'code';
398 413 if (this.input_prompt_number) {
399 414 data.prompt_number = this.input_prompt_number;
400 415 };
401 416 var outputs = this.output_area.toJSON();
402 417 data.outputs = outputs;
403 418 data.language = 'python';
404 419 data.collapsed = this.collapsed;
405 420 return data;
406 421 };
407 422
408 423
409 424 IPython.CodeCell = CodeCell;
410 425
411 426 return IPython;
412 427 }(IPython));
@@ -1,536 +1,551
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