##// END OF EJS Templates
Special handling for CM's vim keyboard mapping.
Brian E. Granger -
Show More
@@ -1,508 +1,533 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // CodeCell
9 // CodeCell
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule CodeCell
15 * @submodule CodeCell
16 */
16 */
17
17
18
18
19 /* local util for codemirror */
19 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21
21
22 /**
22 /**
23 *
23 *
24 * function to delete until previous non blanking space character
24 * function to delete until previous non blanking space character
25 * or first multiple of 4 tabstop.
25 * or first multiple of 4 tabstop.
26 * @private
26 * @private
27 */
27 */
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 var tabsize = cm.getOption('tabSize');
32 var tabsize = cm.getOption('tabSize');
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 var select = cm.getRange(from,cur);
35 var select = cm.getRange(from,cur);
36 if( select.match(/^\ +$/) !== null){
36 if( select.match(/^\ +$/) !== null){
37 cm.replaceRange("",from,cur);
37 cm.replaceRange("",from,cur);
38 } else {
38 } else {
39 cm.deleteH(-1,"char");
39 cm.deleteH(-1,"char");
40 }
40 }
41 };
41 };
42
42
43
43
44 var IPython = (function (IPython) {
44 var IPython = (function (IPython) {
45 "use strict";
45 "use strict";
46
46
47 var utils = IPython.utils;
47 var utils = IPython.utils;
48 var key = IPython.utils.keycodes;
48 var key = IPython.utils.keycodes;
49
49
50 /**
50 /**
51 * A Cell conceived to write code.
51 * A Cell conceived to write code.
52 *
52 *
53 * The kernel doesn't have to be set at creation time, in that case
53 * The kernel doesn't have to be set at creation time, in that case
54 * it will be null and set_kernel has to be called later.
54 * it will be null and set_kernel has to be called later.
55 * @class CodeCell
55 * @class CodeCell
56 * @extends IPython.Cell
56 * @extends IPython.Cell
57 *
57 *
58 * @constructor
58 * @constructor
59 * @param {Object|null} kernel
59 * @param {Object|null} kernel
60 * @param {object|undefined} [options]
60 * @param {object|undefined} [options]
61 * @param [options.cm_config] {object} config to pass to CodeMirror
61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 */
62 */
63 var CodeCell = function (kernel, options) {
63 var CodeCell = function (kernel, options) {
64 this.kernel = kernel || null;
64 this.kernel = kernel || null;
65 this.collapsed = false;
65 this.collapsed = false;
66
66
67 // create all attributed in constructor function
67 // create all attributed in constructor function
68 // even if null for V8 VM optimisation
68 // even if null for V8 VM optimisation
69 this.input_prompt_number = null;
69 this.input_prompt_number = null;
70 this.celltoolbar = null;
70 this.celltoolbar = null;
71 this.output_area = null;
71 this.output_area = null;
72 this.last_msg_id = null;
72 this.last_msg_id = null;
73 this.completer = null;
73 this.completer = null;
74
74
75
75
76 var cm_overwrite_options = {
76 var cm_overwrite_options = {
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 };
78 };
79
79
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81
81
82 IPython.Cell.apply(this,[options]);
82 IPython.Cell.apply(this,[options]);
83
83
84 // Attributes we want to override in this subclass.
84 // Attributes we want to override in this subclass.
85 this.cell_type = "code";
85 this.cell_type = "code";
86
86
87 var that = this;
87 var that = this;
88 this.element.focusout(
88 this.element.focusout(
89 function() { that.auto_highlight(); }
89 function() { that.auto_highlight(); }
90 );
90 );
91 };
91 };
92
92
93 CodeCell.options_default = {
93 CodeCell.options_default = {
94 cm_config : {
94 cm_config : {
95 extraKeys: {
95 extraKeys: {
96 "Tab" : "indentMore",
96 "Tab" : "indentMore",
97 "Shift-Tab" : "indentLess",
97 "Shift-Tab" : "indentLess",
98 "Backspace" : "delSpaceToPrevTabStop",
98 "Backspace" : "delSpaceToPrevTabStop",
99 "Cmd-/" : "toggleComment",
99 "Cmd-/" : "toggleComment",
100 "Ctrl-/" : "toggleComment"
100 "Ctrl-/" : "toggleComment"
101 },
101 },
102 mode: 'ipython',
102 mode: 'ipython',
103 theme: 'ipython',
103 theme: 'ipython',
104 matchBrackets: true
104 matchBrackets: true
105 }
105 }
106 };
106 };
107
107
108
108
109 CodeCell.prototype = new IPython.Cell();
109 CodeCell.prototype = new IPython.Cell();
110
110
111 /**
111 /**
112 * @method auto_highlight
112 * @method auto_highlight
113 */
113 */
114 CodeCell.prototype.auto_highlight = function () {
114 CodeCell.prototype.auto_highlight = function () {
115 this._auto_highlight(IPython.config.cell_magic_highlight);
115 this._auto_highlight(IPython.config.cell_magic_highlight);
116 };
116 };
117
117
118 /** @method create_element */
118 /** @method create_element */
119 CodeCell.prototype.create_element = function () {
119 CodeCell.prototype.create_element = function () {
120 IPython.Cell.prototype.create_element.apply(this, arguments);
120 IPython.Cell.prototype.create_element.apply(this, arguments);
121
121
122 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
122 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
123 cell.attr('tabindex','2');
123 cell.attr('tabindex','2');
124
124
125 var input = $('<div></div>').addClass('input');
125 var input = $('<div></div>').addClass('input');
126 var prompt = $('<div/>').addClass('prompt input_prompt');
126 var prompt = $('<div/>').addClass('prompt input_prompt');
127 var inner_cell = $('<div/>').addClass('inner_cell');
127 var inner_cell = $('<div/>').addClass('inner_cell');
128 this.celltoolbar = new IPython.CellToolbar(this);
128 this.celltoolbar = new IPython.CellToolbar(this);
129 inner_cell.append(this.celltoolbar.element);
129 inner_cell.append(this.celltoolbar.element);
130 var input_area = $('<div/>').addClass('input_area');
130 var input_area = $('<div/>').addClass('input_area');
131 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
131 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
132 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
132 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
133 inner_cell.append(input_area);
133 inner_cell.append(input_area);
134 input.append(prompt).append(inner_cell);
134 input.append(prompt).append(inner_cell);
135 var output = $('<div></div>');
135 var output = $('<div></div>');
136 cell.append(input).append(output);
136 cell.append(input).append(output);
137 this.element = cell;
137 this.element = cell;
138 this.output_area = new IPython.OutputArea(output, true);
138 this.output_area = new IPython.OutputArea(output, true);
139 this.completer = new IPython.Completer(this);
139 this.completer = new IPython.Completer(this);
140 };
140 };
141
141
142 /** @method bind_events */
142 /** @method bind_events */
143 CodeCell.prototype.bind_events = function () {
143 CodeCell.prototype.bind_events = function () {
144 IPython.Cell.prototype.bind_events.apply(this);
144 IPython.Cell.prototype.bind_events.apply(this);
145 var that = this;
145 var that = this;
146
146
147 this.element.focusout(
147 this.element.focusout(
148 function() { that.auto_highlight(); }
148 function() { that.auto_highlight(); }
149 );
149 );
150 };
150 };
151
151
152 CodeCell.prototype.handle_keyevent = function (editor, event) {
152 CodeCell.prototype.handle_keyevent = function (editor, event) {
153
153
154 console.log('CM', this.mode, event.which, event.type)
154 console.log('CM', this.mode, event.which, event.type)
155
155
156 if (this.mode === 'command') {
156 if (this.mode === 'command') {
157 return true;
157 return true;
158 } else if (this.mode === 'edit') {
158 } else if (this.mode === 'edit') {
159 return this.handle_codemirror_keyevent(editor, event);
159 return this.handle_codemirror_keyevent(editor, event);
160 }
160 }
161 };
161 };
162
162
163 /**
163 /**
164 * This method gets called in CodeMirror's onKeyDown/onKeyPress
164 * This method gets called in CodeMirror's onKeyDown/onKeyPress
165 * handlers and is used to provide custom key handling. Its return
165 * handlers and is used to provide custom key handling. Its return
166 * value is used to determine if CodeMirror should ignore the event:
166 * value is used to determine if CodeMirror should ignore the event:
167 * true = ignore, false = don't ignore.
167 * true = ignore, false = don't ignore.
168 * @method handle_codemirror_keyevent
168 * @method handle_codemirror_keyevent
169 */
169 */
170 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
170 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
171
171
172 var that = this;
172 var that = this;
173 // whatever key is pressed, first, cancel the tooltip request before
173 // whatever key is pressed, first, cancel the tooltip request before
174 // they are sent, and remove tooltip if any, except for tab again
174 // they are sent, and remove tooltip if any, except for tab again
175 var tooltip_closed = null;
175 if (event.type === 'keydown' && event.which != key.TAB ) {
176 if (event.type === 'keydown' && event.which != key.TAB ) {
176 IPython.tooltip.remove_and_cancel_tooltip();
177 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
177 }
178 }
178
179
179 var cur = editor.getCursor();
180 var cur = editor.getCursor();
180 if (event.keyCode === key.ENTER){
181 if (event.keyCode === key.ENTER){
181 this.auto_highlight();
182 this.auto_highlight();
182 }
183 }
183
184
184 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
185 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
185 // Always ignore shift-enter in CodeMirror as we handle it.
186 // Always ignore shift-enter in CodeMirror as we handle it.
186 return true;
187 return true;
187 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
188 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
188 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
189 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
189 // browser and keyboard layout !
190 // browser and keyboard layout !
190 // Pressing '(' , request tooltip, don't forget to reappend it
191 // Pressing '(' , request tooltip, don't forget to reappend it
191 // The second argument says to hide the tooltip if the docstring
192 // The second argument says to hide the tooltip if the docstring
192 // is actually empty
193 // is actually empty
193 IPython.tooltip.pending(that, true);
194 IPython.tooltip.pending(that, true);
194 } else if (event.which === key.UPARROW && event.type === 'keydown') {
195 } else if (event.which === key.UPARROW && event.type === 'keydown') {
195 // If we are not at the top, let CM handle the up arrow and
196 // If we are not at the top, let CM handle the up arrow and
196 // prevent the global keydown handler from handling it.
197 // prevent the global keydown handler from handling it.
197 if (!that.at_top()) {
198 if (!that.at_top()) {
198 event.stop();
199 event.stop();
199 return false;
200 return false;
200 } else {
201 } else {
201 return true;
202 return true;
202 }
203 }
203 } else if (event.which === key.ESC) {
204 } else if (event.which === key.ESC && event.type === 'keydown') {
204 return IPython.tooltip.remove_and_cancel_tooltip(true);
205 // First see if the tooltip is active and if so cancel it.
206 if (tooltip_closed) {
207 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
208 // force=true. Because of this it won't actually close the tooltip
209 // if it is in sticky mode. Thus, we have to check again if it is open
210 // and close it with force=true.
211 if (!IPython.tooltip._hidden) {
212 IPython.tooltip.remove_and_cancel_tooltip(true);
213 }
214 // If we closed the tooltip, don't let CM or the global handlers
215 // handle this event.
216 event.stop();
217 return true;
218 }
219 if (that.code_mirror.options.keyMap === "vim-insert") {
220 // vim keyMap is active and in insert mode. In this case we leave vim
221 // insert mode, but remain in notebook edit mode.
222 // Let' CM handle this event and prevent global handling.
223 event.stop();
224 return false;
225 } else {
226 // vim keyMap is not active. Leave notebook edit mode.
227 // Don't let CM handle the event, defer to global handling.
228 return true;
229 }
205 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
230 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
206 // If we are not at the bottom, let CM handle the down arrow and
231 // If we are not at the bottom, let CM handle the down arrow and
207 // prevent the global keydown handler from handling it.
232 // prevent the global keydown handler from handling it.
208 if (!that.at_bottom()) {
233 if (!that.at_bottom()) {
209 event.stop();
234 event.stop();
210 return false;
235 return false;
211 } else {
236 } else {
212 return true;
237 return true;
213 }
238 }
214 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
239 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
215 if (editor.somethingSelected()){
240 if (editor.somethingSelected()){
216 var anchor = editor.getCursor("anchor");
241 var anchor = editor.getCursor("anchor");
217 var head = editor.getCursor("head");
242 var head = editor.getCursor("head");
218 if( anchor.line != head.line){
243 if( anchor.line != head.line){
219 return false;
244 return false;
220 }
245 }
221 }
246 }
222 IPython.tooltip.request(that);
247 IPython.tooltip.request(that);
223 event.stop();
248 event.stop();
224 return true;
249 return true;
225 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
250 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
226 // Tab completion.
251 // Tab completion.
227 IPython.tooltip.remove_and_cancel_tooltip();
252 IPython.tooltip.remove_and_cancel_tooltip();
228 if (editor.somethingSelected()) {
253 if (editor.somethingSelected()) {
229 return false;
254 return false;
230 }
255 }
231 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
256 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
232 if (pre_cursor.trim() === "") {
257 if (pre_cursor.trim() === "") {
233 // Don't autocomplete if the part of the line before the cursor
258 // Don't autocomplete if the part of the line before the cursor
234 // is empty. In this case, let CodeMirror handle indentation.
259 // is empty. In this case, let CodeMirror handle indentation.
235 return false;
260 return false;
236 } else {
261 } else {
237 event.stop();
262 event.stop();
238 this.completer.startCompletion();
263 this.completer.startCompletion();
239 return true;
264 return true;
240 }
265 }
241 } else {
266 } else {
242 // keypress/keyup also trigger on TAB press, and we don't want to
267 // keypress/keyup also trigger on TAB press, and we don't want to
243 // use those to disable tab completion.
268 // use those to disable tab completion.
244 return false;
269 return false;
245 }
270 }
246 return false;
271 return false;
247 };
272 };
248
273
249 // Kernel related calls.
274 // Kernel related calls.
250
275
251 CodeCell.prototype.set_kernel = function (kernel) {
276 CodeCell.prototype.set_kernel = function (kernel) {
252 this.kernel = kernel;
277 this.kernel = kernel;
253 };
278 };
254
279
255 /**
280 /**
256 * Execute current code cell to the kernel
281 * Execute current code cell to the kernel
257 * @method execute
282 * @method execute
258 */
283 */
259 CodeCell.prototype.execute = function () {
284 CodeCell.prototype.execute = function () {
260 this.output_area.clear_output();
285 this.output_area.clear_output();
261 this.set_input_prompt('*');
286 this.set_input_prompt('*');
262 this.element.addClass("running");
287 this.element.addClass("running");
263 if (this.last_msg_id) {
288 if (this.last_msg_id) {
264 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
289 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
265 }
290 }
266 var callbacks = this.get_callbacks();
291 var callbacks = this.get_callbacks();
267
292
268 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
293 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
269 };
294 };
270
295
271 /**
296 /**
272 * Construct the default callbacks for
297 * Construct the default callbacks for
273 * @method get_callbacks
298 * @method get_callbacks
274 */
299 */
275 CodeCell.prototype.get_callbacks = function () {
300 CodeCell.prototype.get_callbacks = function () {
276 return {
301 return {
277 shell : {
302 shell : {
278 reply : $.proxy(this._handle_execute_reply, this),
303 reply : $.proxy(this._handle_execute_reply, this),
279 payload : {
304 payload : {
280 set_next_input : $.proxy(this._handle_set_next_input, this),
305 set_next_input : $.proxy(this._handle_set_next_input, this),
281 page : $.proxy(this._open_with_pager, this)
306 page : $.proxy(this._open_with_pager, this)
282 }
307 }
283 },
308 },
284 iopub : {
309 iopub : {
285 output : $.proxy(this.output_area.handle_output, this.output_area),
310 output : $.proxy(this.output_area.handle_output, this.output_area),
286 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
311 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
287 },
312 },
288 input : $.proxy(this._handle_input_request, this)
313 input : $.proxy(this._handle_input_request, this)
289 };
314 };
290 };
315 };
291
316
292 CodeCell.prototype._open_with_pager = function (payload) {
317 CodeCell.prototype._open_with_pager = function (payload) {
293 $([IPython.events]).trigger('open_with_text.Pager', payload);
318 $([IPython.events]).trigger('open_with_text.Pager', payload);
294 };
319 };
295
320
296 /**
321 /**
297 * @method _handle_execute_reply
322 * @method _handle_execute_reply
298 * @private
323 * @private
299 */
324 */
300 CodeCell.prototype._handle_execute_reply = function (msg) {
325 CodeCell.prototype._handle_execute_reply = function (msg) {
301 this.set_input_prompt(msg.content.execution_count);
326 this.set_input_prompt(msg.content.execution_count);
302 this.element.removeClass("running");
327 this.element.removeClass("running");
303 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
328 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
304 };
329 };
305
330
306 /**
331 /**
307 * @method _handle_set_next_input
332 * @method _handle_set_next_input
308 * @private
333 * @private
309 */
334 */
310 CodeCell.prototype._handle_set_next_input = function (payload) {
335 CodeCell.prototype._handle_set_next_input = function (payload) {
311 var data = {'cell': this, 'text': payload.text};
336 var data = {'cell': this, 'text': payload.text};
312 $([IPython.events]).trigger('set_next_input.Notebook', data);
337 $([IPython.events]).trigger('set_next_input.Notebook', data);
313 };
338 };
314
339
315 /**
340 /**
316 * @method _handle_input_request
341 * @method _handle_input_request
317 * @private
342 * @private
318 */
343 */
319 CodeCell.prototype._handle_input_request = function (msg) {
344 CodeCell.prototype._handle_input_request = function (msg) {
320 this.output_area.append_raw_input(msg);
345 this.output_area.append_raw_input(msg);
321 };
346 };
322
347
323
348
324 // Basic cell manipulation.
349 // Basic cell manipulation.
325
350
326 CodeCell.prototype.select = function () {
351 CodeCell.prototype.select = function () {
327 var cont = IPython.Cell.prototype.select.apply(this);
352 var cont = IPython.Cell.prototype.select.apply(this);
328 if (cont) {
353 if (cont) {
329 this.code_mirror.refresh();
354 this.code_mirror.refresh();
330 this.auto_highlight();
355 this.auto_highlight();
331 };
356 };
332 return cont;
357 return cont;
333 };
358 };
334
359
335 CodeCell.prototype.render = function () {
360 CodeCell.prototype.render = function () {
336 var cont = IPython.Cell.prototype.render.apply(this);
361 var cont = IPython.Cell.prototype.render.apply(this);
337 // Always execute, even if we are already in the rendered state
362 // Always execute, even if we are already in the rendered state
338 return cont;
363 return cont;
339 };
364 };
340
365
341 CodeCell.prototype.unrender = function () {
366 CodeCell.prototype.unrender = function () {
342 // CodeCell is always rendered
367 // CodeCell is always rendered
343 return false;
368 return false;
344 };
369 };
345
370
346 CodeCell.prototype.command_mode = function () {
371 CodeCell.prototype.command_mode = function () {
347 var cont = IPython.Cell.prototype.command_mode.apply(this);
372 var cont = IPython.Cell.prototype.command_mode.apply(this);
348 if (cont) {
373 if (cont) {
349 this.focus_cell();
374 this.focus_cell();
350 };
375 };
351 return cont;
376 return cont;
352 }
377 }
353
378
354 CodeCell.prototype.edit_mode = function () {
379 CodeCell.prototype.edit_mode = function () {
355 var cont = IPython.Cell.prototype.edit_mode.apply(this);
380 var cont = IPython.Cell.prototype.edit_mode.apply(this);
356 if (cont) {
381 if (cont) {
357 this.focus_editor();
382 this.focus_editor();
358 };
383 };
359 return cont;
384 return cont;
360 }
385 }
361
386
362 CodeCell.prototype.select_all = function () {
387 CodeCell.prototype.select_all = function () {
363 var start = {line: 0, ch: 0};
388 var start = {line: 0, ch: 0};
364 var nlines = this.code_mirror.lineCount();
389 var nlines = this.code_mirror.lineCount();
365 var last_line = this.code_mirror.getLine(nlines-1);
390 var last_line = this.code_mirror.getLine(nlines-1);
366 var end = {line: nlines-1, ch: last_line.length};
391 var end = {line: nlines-1, ch: last_line.length};
367 this.code_mirror.setSelection(start, end);
392 this.code_mirror.setSelection(start, end);
368 };
393 };
369
394
370
395
371 CodeCell.prototype.collapse = function () {
396 CodeCell.prototype.collapse = function () {
372 this.collapsed = true;
397 this.collapsed = true;
373 this.output_area.collapse();
398 this.output_area.collapse();
374 };
399 };
375
400
376
401
377 CodeCell.prototype.expand = function () {
402 CodeCell.prototype.expand = function () {
378 this.collapsed = false;
403 this.collapsed = false;
379 this.output_area.expand();
404 this.output_area.expand();
380 };
405 };
381
406
382
407
383 CodeCell.prototype.toggle_output = function () {
408 CodeCell.prototype.toggle_output = function () {
384 this.collapsed = Boolean(1 - this.collapsed);
409 this.collapsed = Boolean(1 - this.collapsed);
385 this.output_area.toggle_output();
410 this.output_area.toggle_output();
386 };
411 };
387
412
388
413
389 CodeCell.prototype.toggle_output_scroll = function () {
414 CodeCell.prototype.toggle_output_scroll = function () {
390 this.output_area.toggle_scroll();
415 this.output_area.toggle_scroll();
391 };
416 };
392
417
393
418
394 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
419 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
395 var ns = prompt_value || "&nbsp;";
420 var ns = prompt_value || "&nbsp;";
396 return 'In&nbsp;[' + ns + ']:';
421 return 'In&nbsp;[' + ns + ']:';
397 };
422 };
398
423
399 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
424 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
400 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
425 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
401 for(var i=1; i < lines_number; i++) {
426 for(var i=1; i < lines_number; i++) {
402 html.push(['...:']);
427 html.push(['...:']);
403 }
428 }
404 return html.join('<br/>');
429 return html.join('<br/>');
405 };
430 };
406
431
407 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
432 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
408
433
409
434
410 CodeCell.prototype.set_input_prompt = function (number) {
435 CodeCell.prototype.set_input_prompt = function (number) {
411 var nline = 1;
436 var nline = 1;
412 if (this.code_mirror !== undefined) {
437 if (this.code_mirror !== undefined) {
413 nline = this.code_mirror.lineCount();
438 nline = this.code_mirror.lineCount();
414 }
439 }
415 this.input_prompt_number = number;
440 this.input_prompt_number = number;
416 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
441 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
417 this.element.find('div.input_prompt').html(prompt_html);
442 this.element.find('div.input_prompt').html(prompt_html);
418 };
443 };
419
444
420
445
421 CodeCell.prototype.clear_input = function () {
446 CodeCell.prototype.clear_input = function () {
422 this.code_mirror.setValue('');
447 this.code_mirror.setValue('');
423 };
448 };
424
449
425
450
426 CodeCell.prototype.get_text = function () {
451 CodeCell.prototype.get_text = function () {
427 return this.code_mirror.getValue();
452 return this.code_mirror.getValue();
428 };
453 };
429
454
430
455
431 CodeCell.prototype.set_text = function (code) {
456 CodeCell.prototype.set_text = function (code) {
432 return this.code_mirror.setValue(code);
457 return this.code_mirror.setValue(code);
433 };
458 };
434
459
435
460
436 CodeCell.prototype.at_top = function () {
461 CodeCell.prototype.at_top = function () {
437 var cursor = this.code_mirror.getCursor();
462 var cursor = this.code_mirror.getCursor();
438 if (cursor.line === 0 && cursor.ch === 0) {
463 if (cursor.line === 0 && cursor.ch === 0) {
439 return true;
464 return true;
440 } else {
465 } else {
441 return false;
466 return false;
442 }
467 }
443 };
468 };
444
469
445
470
446 CodeCell.prototype.at_bottom = function () {
471 CodeCell.prototype.at_bottom = function () {
447 var cursor = this.code_mirror.getCursor();
472 var cursor = this.code_mirror.getCursor();
448 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
473 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
449 return true;
474 return true;
450 } else {
475 } else {
451 return false;
476 return false;
452 }
477 }
453 };
478 };
454
479
455
480
456 CodeCell.prototype.clear_output = function (wait) {
481 CodeCell.prototype.clear_output = function (wait) {
457 this.output_area.clear_output(wait);
482 this.output_area.clear_output(wait);
458 };
483 };
459
484
460
485
461 // JSON serialization
486 // JSON serialization
462
487
463 CodeCell.prototype.fromJSON = function (data) {
488 CodeCell.prototype.fromJSON = function (data) {
464 IPython.Cell.prototype.fromJSON.apply(this, arguments);
489 IPython.Cell.prototype.fromJSON.apply(this, arguments);
465 if (data.cell_type === 'code') {
490 if (data.cell_type === 'code') {
466 if (data.input !== undefined) {
491 if (data.input !== undefined) {
467 this.set_text(data.input);
492 this.set_text(data.input);
468 // make this value the starting point, so that we can only undo
493 // make this value the starting point, so that we can only undo
469 // to this state, instead of a blank cell
494 // to this state, instead of a blank cell
470 this.code_mirror.clearHistory();
495 this.code_mirror.clearHistory();
471 this.auto_highlight();
496 this.auto_highlight();
472 }
497 }
473 if (data.prompt_number !== undefined) {
498 if (data.prompt_number !== undefined) {
474 this.set_input_prompt(data.prompt_number);
499 this.set_input_prompt(data.prompt_number);
475 } else {
500 } else {
476 this.set_input_prompt();
501 this.set_input_prompt();
477 }
502 }
478 this.output_area.fromJSON(data.outputs);
503 this.output_area.fromJSON(data.outputs);
479 if (data.collapsed !== undefined) {
504 if (data.collapsed !== undefined) {
480 if (data.collapsed) {
505 if (data.collapsed) {
481 this.collapse();
506 this.collapse();
482 } else {
507 } else {
483 this.expand();
508 this.expand();
484 }
509 }
485 }
510 }
486 }
511 }
487 };
512 };
488
513
489
514
490 CodeCell.prototype.toJSON = function () {
515 CodeCell.prototype.toJSON = function () {
491 var data = IPython.Cell.prototype.toJSON.apply(this);
516 var data = IPython.Cell.prototype.toJSON.apply(this);
492 data.input = this.get_text();
517 data.input = this.get_text();
493 // is finite protect against undefined and '*' value
518 // is finite protect against undefined and '*' value
494 if (isFinite(this.input_prompt_number)) {
519 if (isFinite(this.input_prompt_number)) {
495 data.prompt_number = this.input_prompt_number;
520 data.prompt_number = this.input_prompt_number;
496 }
521 }
497 var outputs = this.output_area.toJSON();
522 var outputs = this.output_area.toJSON();
498 data.outputs = outputs;
523 data.outputs = outputs;
499 data.language = 'python';
524 data.language = 'python';
500 data.collapsed = this.collapsed;
525 data.collapsed = this.collapsed;
501 return data;
526 return data;
502 };
527 };
503
528
504
529
505 IPython.CodeCell = CodeCell;
530 IPython.CodeCell = CodeCell;
506
531
507 return IPython;
532 return IPython;
508 }(IPython));
533 }(IPython));
@@ -1,589 +1,601 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // TextCell
9 // TextCell
10 //============================================================================
10 //============================================================================
11
11
12
12
13
13
14 /**
14 /**
15 A module that allow to create different type of Text Cell
15 A module that allow to create different type of Text Cell
16 @module IPython
16 @module IPython
17 @namespace IPython
17 @namespace IPython
18 */
18 */
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 // TextCell base class
22 // TextCell base class
23 var key = IPython.utils.keycodes;
23 var key = IPython.utils.keycodes;
24
24
25 /**
25 /**
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
27 * cell start as not redered.
27 * cell start as not redered.
28 *
28 *
29 * @class TextCell
29 * @class TextCell
30 * @constructor TextCell
30 * @constructor TextCell
31 * @extend IPython.Cell
31 * @extend IPython.Cell
32 * @param {object|undefined} [options]
32 * @param {object|undefined} [options]
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
35 */
35 */
36 var TextCell = function (options) {
36 var TextCell = function (options) {
37 // in all TextCell/Cell subclasses
37 // in all TextCell/Cell subclasses
38 // do not assign most of members here, just pass it down
38 // do not assign most of members here, just pass it down
39 // in the options dict potentially overwriting what you wish.
39 // in the options dict potentially overwriting what you wish.
40 // they will be assigned in the base class.
40 // they will be assigned in the base class.
41
41
42 // we cannot put this as a class key as it has handle to "this".
42 // we cannot put this as a class key as it has handle to "this".
43 var cm_overwrite_options = {
43 var cm_overwrite_options = {
44 onKeyEvent: $.proxy(this.handle_keyevent,this)
44 onKeyEvent: $.proxy(this.handle_keyevent,this)
45 };
45 };
46
46
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
48
48
49 this.cell_type = this.cell_type || 'text';
49 this.cell_type = this.cell_type || 'text';
50
50
51 IPython.Cell.apply(this, [options]);
51 IPython.Cell.apply(this, [options]);
52
52
53 this.rendered = false;
53 this.rendered = false;
54 };
54 };
55
55
56 TextCell.prototype = new IPython.Cell();
56 TextCell.prototype = new IPython.Cell();
57
57
58 TextCell.options_default = {
58 TextCell.options_default = {
59 cm_config : {
59 cm_config : {
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
61 mode: 'htmlmixed',
61 mode: 'htmlmixed',
62 lineWrapping : true,
62 lineWrapping : true,
63 }
63 }
64 };
64 };
65
65
66
66
67 /**
67 /**
68 * Create the DOM element of the TextCell
68 * Create the DOM element of the TextCell
69 * @method create_element
69 * @method create_element
70 * @private
70 * @private
71 */
71 */
72 TextCell.prototype.create_element = function () {
72 TextCell.prototype.create_element = function () {
73 IPython.Cell.prototype.create_element.apply(this, arguments);
73 IPython.Cell.prototype.create_element.apply(this, arguments);
74
74
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
76 cell.attr('tabindex','2');
76 cell.attr('tabindex','2');
77
77
78 var prompt = $('<div/>').addClass('prompt input_prompt');
78 var prompt = $('<div/>').addClass('prompt input_prompt');
79 cell.append(prompt);
79 cell.append(prompt);
80 var inner_cell = $('<div/>').addClass('inner_cell');
80 var inner_cell = $('<div/>').addClass('inner_cell');
81 this.celltoolbar = new IPython.CellToolbar(this);
81 this.celltoolbar = new IPython.CellToolbar(this);
82 inner_cell.append(this.celltoolbar.element);
82 inner_cell.append(this.celltoolbar.element);
83 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
83 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
84 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
84 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
85 // The tabindex=-1 makes this div focusable.
85 // The tabindex=-1 makes this div focusable.
86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
87 addClass('rendered_html').attr('tabindex','-1');
87 addClass('rendered_html').attr('tabindex','-1');
88 inner_cell.append(input_area).append(render_area);
88 inner_cell.append(input_area).append(render_area);
89 cell.append(inner_cell);
89 cell.append(inner_cell);
90 this.element = cell;
90 this.element = cell;
91 };
91 };
92
92
93
93
94 /**
94 /**
95 * Bind the DOM evet to cell actions
95 * Bind the DOM evet to cell actions
96 * Need to be called after TextCell.create_element
96 * Need to be called after TextCell.create_element
97 * @private
97 * @private
98 * @method bind_event
98 * @method bind_event
99 */
99 */
100 TextCell.prototype.bind_events = function () {
100 TextCell.prototype.bind_events = function () {
101 IPython.Cell.prototype.bind_events.apply(this);
101 IPython.Cell.prototype.bind_events.apply(this);
102 var that = this;
102 var that = this;
103
103
104 this.element.dblclick(function () {
104 this.element.dblclick(function () {
105 if (that.selected === false) {
105 if (that.selected === false) {
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
107 };
107 };
108 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
108 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
109 });
109 });
110 };
110 };
111
111
112 TextCell.prototype.handle_keyevent = function (editor, event) {
112 TextCell.prototype.handle_keyevent = function (editor, event) {
113
113
114 console.log('CM', this.mode, event.which, event.type)
114 console.log('CM', this.mode, event.which, event.type)
115
115
116 if (this.mode === 'command') {
116 if (this.mode === 'command') {
117 return true;
117 return true;
118 } else if (this.mode === 'edit') {
118 } else if (this.mode === 'edit') {
119 return this.handle_codemirror_keyevent(editor, event);
119 return this.handle_codemirror_keyevent(editor, event);
120 }
120 }
121 };
121 };
122
122
123 /**
123 /**
124 * This method gets called in CodeMirror's onKeyDown/onKeyPress
124 * This method gets called in CodeMirror's onKeyDown/onKeyPress
125 * handlers and is used to provide custom key handling.
125 * handlers and is used to provide custom key handling.
126 *
126 *
127 * Subclass should override this method to have custom handeling
127 * Subclass should override this method to have custom handeling
128 *
128 *
129 * @method handle_codemirror_keyevent
129 * @method handle_codemirror_keyevent
130 * @param {CodeMirror} editor - The codemirror instance bound to the cell
130 * @param {CodeMirror} editor - The codemirror instance bound to the cell
131 * @param {event} event -
131 * @param {event} event -
132 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
132 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
133 */
133 */
134 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
134 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
135 var that = this;
135 var that = this;
136
136
137 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
137 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
138 // Always ignore shift-enter in CodeMirror as we handle it.
138 // Always ignore shift-enter in CodeMirror as we handle it.
139 return true;
139 return true;
140 } else if (event.which === key.UPARROW && event.type === 'keydown') {
140 } else if (event.which === key.UPARROW && event.type === 'keydown') {
141 // If we are not at the top, let CM handle the up arrow and
141 // If we are not at the top, let CM handle the up arrow and
142 // prevent the global keydown handler from handling it.
142 // prevent the global keydown handler from handling it.
143 if (!that.at_top()) {
143 if (!that.at_top()) {
144 event.stop();
144 event.stop();
145 return false;
145 return false;
146 } else {
146 } else {
147 return true;
147 return true;
148 };
148 };
149 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
149 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
150 // If we are not at the bottom, let CM handle the down arrow and
150 // If we are not at the bottom, let CM handle the down arrow and
151 // prevent the global keydown handler from handling it.
151 // prevent the global keydown handler from handling it.
152 if (!that.at_bottom()) {
152 if (!that.at_bottom()) {
153 event.stop();
153 event.stop();
154 return false;
154 return false;
155 } else {
155 } else {
156 return true;
156 return true;
157 };
157 };
158 } else if (event.which === key.ESC && event.type === 'keydown') {
159 if (that.code_mirror.options.keyMap === "vim-insert") {
160 // vim keyMap is active and in insert mode. In this case we leave vim
161 // insert mode, but remain in notebook edit mode.
162 // Let' CM handle this event and prevent global handling.
163 event.stop();
164 return false;
165 } else {
166 // vim keyMap is not active. Leave notebook edit mode.
167 // Don't let CM handle the event, defer to global handling.
168 return true;
169 }
158 }
170 }
159 return false;
171 return false;
160 };
172 };
161
173
162 // Cell level actions
174 // Cell level actions
163
175
164 TextCell.prototype.select = function () {
176 TextCell.prototype.select = function () {
165 var cont = IPython.Cell.prototype.select.apply(this);
177 var cont = IPython.Cell.prototype.select.apply(this);
166 if (cont) {
178 if (cont) {
167 if (this.mode === 'edit') {
179 if (this.mode === 'edit') {
168 this.code_mirror.refresh();
180 this.code_mirror.refresh();
169 }
181 }
170 };
182 };
171 return cont;
183 return cont;
172 };
184 };
173
185
174 TextCell.prototype.unrender = function () {
186 TextCell.prototype.unrender = function () {
175 if (this.read_only) return;
187 if (this.read_only) return;
176 var cont = IPython.Cell.prototype.unrender.apply(this);
188 var cont = IPython.Cell.prototype.unrender.apply(this);
177 if (cont) {
189 if (cont) {
178 var text_cell = this.element;
190 var text_cell = this.element;
179 var output = text_cell.find("div.text_cell_render");
191 var output = text_cell.find("div.text_cell_render");
180 output.hide();
192 output.hide();
181 text_cell.find('div.text_cell_input').show();
193 text_cell.find('div.text_cell_input').show();
182 if (this.get_text() === this.placeholder) {
194 if (this.get_text() === this.placeholder) {
183 this.set_text('');
195 this.set_text('');
184 this.refresh();
196 this.refresh();
185 }
197 }
186
198
187 };
199 };
188 return cont;
200 return cont;
189 };
201 };
190
202
191 TextCell.prototype.execute = function () {
203 TextCell.prototype.execute = function () {
192 this.render();
204 this.render();
193 };
205 };
194
206
195 TextCell.prototype.command_mode = function () {
207 TextCell.prototype.command_mode = function () {
196 var cont = IPython.Cell.prototype.command_mode.apply(this);
208 var cont = IPython.Cell.prototype.command_mode.apply(this);
197 if (cont) {
209 if (cont) {
198 this.focus_cell();
210 this.focus_cell();
199 };
211 };
200 return cont;
212 return cont;
201 }
213 }
202
214
203 TextCell.prototype.edit_mode = function () {
215 TextCell.prototype.edit_mode = function () {
204 var cont = IPython.Cell.prototype.edit_mode.apply(this);
216 var cont = IPython.Cell.prototype.edit_mode.apply(this);
205 if (cont) {
217 if (cont) {
206 this.unrender();
218 this.unrender();
207 this.focus_editor();
219 this.focus_editor();
208 };
220 };
209 return cont;
221 return cont;
210 }
222 }
211
223
212 /**
224 /**
213 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
225 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
214 * @method get_text
226 * @method get_text
215 * @retrun {string} CodeMirror current text value
227 * @retrun {string} CodeMirror current text value
216 */
228 */
217 TextCell.prototype.get_text = function() {
229 TextCell.prototype.get_text = function() {
218 return this.code_mirror.getValue();
230 return this.code_mirror.getValue();
219 };
231 };
220
232
221 /**
233 /**
222 * @param {string} text - Codemiror text value
234 * @param {string} text - Codemiror text value
223 * @see TextCell#get_text
235 * @see TextCell#get_text
224 * @method set_text
236 * @method set_text
225 * */
237 * */
226 TextCell.prototype.set_text = function(text) {
238 TextCell.prototype.set_text = function(text) {
227 this.code_mirror.setValue(text);
239 this.code_mirror.setValue(text);
228 this.code_mirror.refresh();
240 this.code_mirror.refresh();
229 };
241 };
230
242
231 /**
243 /**
232 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
244 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
233 * @method get_rendered
245 * @method get_rendered
234 * @return {html} html of rendered element
246 * @return {html} html of rendered element
235 * */
247 * */
236 TextCell.prototype.get_rendered = function() {
248 TextCell.prototype.get_rendered = function() {
237 return this.element.find('div.text_cell_render').html();
249 return this.element.find('div.text_cell_render').html();
238 };
250 };
239
251
240 /**
252 /**
241 * @method set_rendered
253 * @method set_rendered
242 */
254 */
243 TextCell.prototype.set_rendered = function(text) {
255 TextCell.prototype.set_rendered = function(text) {
244 this.element.find('div.text_cell_render').html(text);
256 this.element.find('div.text_cell_render').html(text);
245 };
257 };
246
258
247 /**
259 /**
248 * @method at_top
260 * @method at_top
249 * @return {Boolean}
261 * @return {Boolean}
250 */
262 */
251 TextCell.prototype.at_top = function () {
263 TextCell.prototype.at_top = function () {
252 if (this.rendered) {
264 if (this.rendered) {
253 return true;
265 return true;
254 } else {
266 } else {
255 var cursor = this.code_mirror.getCursor();
267 var cursor = this.code_mirror.getCursor();
256 if (cursor.line === 0 && cursor.ch === 0) {
268 if (cursor.line === 0 && cursor.ch === 0) {
257 return true;
269 return true;
258 } else {
270 } else {
259 return false;
271 return false;
260 };
272 };
261 };
273 };
262 };
274 };
263
275
264 /**
276 /**
265 * @method at_bottom
277 * @method at_bottom
266 * @return {Boolean}
278 * @return {Boolean}
267 * */
279 * */
268 TextCell.prototype.at_bottom = function () {
280 TextCell.prototype.at_bottom = function () {
269 if (this.rendered) {
281 if (this.rendered) {
270 return true;
282 return true;
271 } else {
283 } else {
272 var cursor = this.code_mirror.getCursor();
284 var cursor = this.code_mirror.getCursor();
273 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
285 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
274 return true;
286 return true;
275 } else {
287 } else {
276 return false;
288 return false;
277 };
289 };
278 };
290 };
279 };
291 };
280
292
281 /**
293 /**
282 * Create Text cell from JSON
294 * Create Text cell from JSON
283 * @param {json} data - JSON serialized text-cell
295 * @param {json} data - JSON serialized text-cell
284 * @method fromJSON
296 * @method fromJSON
285 */
297 */
286 TextCell.prototype.fromJSON = function (data) {
298 TextCell.prototype.fromJSON = function (data) {
287 IPython.Cell.prototype.fromJSON.apply(this, arguments);
299 IPython.Cell.prototype.fromJSON.apply(this, arguments);
288 if (data.cell_type === this.cell_type) {
300 if (data.cell_type === this.cell_type) {
289 if (data.source !== undefined) {
301 if (data.source !== undefined) {
290 this.set_text(data.source);
302 this.set_text(data.source);
291 // make this value the starting point, so that we can only undo
303 // make this value the starting point, so that we can only undo
292 // to this state, instead of a blank cell
304 // to this state, instead of a blank cell
293 this.code_mirror.clearHistory();
305 this.code_mirror.clearHistory();
294 this.set_rendered(data.rendered || '');
306 this.set_rendered(data.rendered || '');
295 this.rendered = false;
307 this.rendered = false;
296 this.render();
308 this.render();
297 }
309 }
298 }
310 }
299 };
311 };
300
312
301 /** Generate JSON from cell
313 /** Generate JSON from cell
302 * @return {object} cell data serialised to json
314 * @return {object} cell data serialised to json
303 */
315 */
304 TextCell.prototype.toJSON = function () {
316 TextCell.prototype.toJSON = function () {
305 var data = IPython.Cell.prototype.toJSON.apply(this);
317 var data = IPython.Cell.prototype.toJSON.apply(this);
306 data.source = this.get_text();
318 data.source = this.get_text();
307 if (data.source == this.placeholder) {
319 if (data.source == this.placeholder) {
308 data.source = "";
320 data.source = "";
309 }
321 }
310 return data;
322 return data;
311 };
323 };
312
324
313
325
314 /**
326 /**
315 * @class MarkdownCell
327 * @class MarkdownCell
316 * @constructor MarkdownCell
328 * @constructor MarkdownCell
317 * @extends IPython.HTMLCell
329 * @extends IPython.HTMLCell
318 */
330 */
319 var MarkdownCell = function (options) {
331 var MarkdownCell = function (options) {
320 options = this.mergeopt(MarkdownCell, options);
332 options = this.mergeopt(MarkdownCell, options);
321
333
322 this.cell_type = 'markdown';
334 this.cell_type = 'markdown';
323 TextCell.apply(this, [options]);
335 TextCell.apply(this, [options]);
324 };
336 };
325
337
326 MarkdownCell.options_default = {
338 MarkdownCell.options_default = {
327 cm_config: {
339 cm_config: {
328 mode: 'gfm'
340 mode: 'gfm'
329 },
341 },
330 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
342 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
331 }
343 }
332
344
333 MarkdownCell.prototype = new TextCell();
345 MarkdownCell.prototype = new TextCell();
334
346
335 /**
347 /**
336 * @method render
348 * @method render
337 */
349 */
338 MarkdownCell.prototype.render = function () {
350 MarkdownCell.prototype.render = function () {
339 var cont = IPython.TextCell.prototype.render.apply(this);
351 var cont = IPython.TextCell.prototype.render.apply(this);
340 if (cont) {
352 if (cont) {
341 var text = this.get_text();
353 var text = this.get_text();
342 var math = null;
354 var math = null;
343 if (text === "") { text = this.placeholder; }
355 if (text === "") { text = this.placeholder; }
344 var text_and_math = IPython.mathjaxutils.remove_math(text);
356 var text_and_math = IPython.mathjaxutils.remove_math(text);
345 text = text_and_math[0];
357 text = text_and_math[0];
346 math = text_and_math[1];
358 math = text_and_math[1];
347 var html = marked.parser(marked.lexer(text));
359 var html = marked.parser(marked.lexer(text));
348 html = $(IPython.mathjaxutils.replace_math(html, math));
360 html = $(IPython.mathjaxutils.replace_math(html, math));
349 // links in markdown cells should open in new tabs
361 // links in markdown cells should open in new tabs
350 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
362 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
351 try {
363 try {
352 this.set_rendered(html);
364 this.set_rendered(html);
353 } catch (e) {
365 } catch (e) {
354 console.log("Error running Javascript in Markdown:");
366 console.log("Error running Javascript in Markdown:");
355 console.log(e);
367 console.log(e);
356 this.set_rendered($("<div/>").addClass("js-error").html(
368 this.set_rendered($("<div/>").addClass("js-error").html(
357 "Error rendering Markdown!<br/>" + e.toString())
369 "Error rendering Markdown!<br/>" + e.toString())
358 );
370 );
359 }
371 }
360 this.element.find('div.text_cell_input').hide();
372 this.element.find('div.text_cell_input').hide();
361 this.element.find("div.text_cell_render").show();
373 this.element.find("div.text_cell_render").show();
362 this.typeset()
374 this.typeset()
363 };
375 };
364 return cont;
376 return cont;
365 };
377 };
366
378
367
379
368 // RawCell
380 // RawCell
369
381
370 /**
382 /**
371 * @class RawCell
383 * @class RawCell
372 * @constructor RawCell
384 * @constructor RawCell
373 * @extends IPython.TextCell
385 * @extends IPython.TextCell
374 */
386 */
375 var RawCell = function (options) {
387 var RawCell = function (options) {
376
388
377 options = this.mergeopt(RawCell,options)
389 options = this.mergeopt(RawCell,options)
378 TextCell.apply(this, [options]);
390 TextCell.apply(this, [options]);
379 this.cell_type = 'raw';
391 this.cell_type = 'raw';
380 // RawCell should always hide its rendered div
392 // RawCell should always hide its rendered div
381 this.element.find('div.text_cell_render').hide();
393 this.element.find('div.text_cell_render').hide();
382 };
394 };
383
395
384 RawCell.options_default = {
396 RawCell.options_default = {
385 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
397 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
386 "It will not be rendered in the notebook.\n" +
398 "It will not be rendered in the notebook.\n" +
387 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
399 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
388 };
400 };
389
401
390 RawCell.prototype = new TextCell();
402 RawCell.prototype = new TextCell();
391
403
392 /** @method bind_events **/
404 /** @method bind_events **/
393 RawCell.prototype.bind_events = function () {
405 RawCell.prototype.bind_events = function () {
394 TextCell.prototype.bind_events.apply(this);
406 TextCell.prototype.bind_events.apply(this);
395 var that = this
407 var that = this
396 this.element.focusout(function() {
408 this.element.focusout(function() {
397 that.auto_highlight();
409 that.auto_highlight();
398 });
410 });
399 };
411 };
400
412
401 /**
413 /**
402 * Trigger autodetection of highlight scheme for current cell
414 * Trigger autodetection of highlight scheme for current cell
403 * @method auto_highlight
415 * @method auto_highlight
404 */
416 */
405 RawCell.prototype.auto_highlight = function () {
417 RawCell.prototype.auto_highlight = function () {
406 this._auto_highlight(IPython.config.raw_cell_highlight);
418 this._auto_highlight(IPython.config.raw_cell_highlight);
407 };
419 };
408
420
409 /** @method render **/
421 /** @method render **/
410 RawCell.prototype.render = function () {
422 RawCell.prototype.render = function () {
411 // Make sure that this cell type can never be rendered
423 // Make sure that this cell type can never be rendered
412 if (this.rendered) {
424 if (this.rendered) {
413 this.unrender();
425 this.unrender();
414 }
426 }
415 var text = this.get_text();
427 var text = this.get_text();
416 if (text === "") { text = this.placeholder; }
428 if (text === "") { text = this.placeholder; }
417 this.set_text(text);
429 this.set_text(text);
418 };
430 };
419
431
420
432
421 /** @method handle_codemirror_keyevent **/
433 /** @method handle_codemirror_keyevent **/
422 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
434 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
423
435
424 var that = this;
436 var that = this;
425 if (this.mode === 'command') {
437 if (this.mode === 'command') {
426 return false
438 return false
427 } else if (this.mode === 'edit') {
439 } else if (this.mode === 'edit') {
428 // TODO: review these handlers...
440 // TODO: review these handlers...
429 if (event.which === key.UPARROW && event.type === 'keydown') {
441 if (event.which === key.UPARROW && event.type === 'keydown') {
430 // If we are not at the top, let CM handle the up arrow and
442 // If we are not at the top, let CM handle the up arrow and
431 // prevent the global keydown handler from handling it.
443 // prevent the global keydown handler from handling it.
432 if (!that.at_top()) {
444 if (!that.at_top()) {
433 event.stop();
445 event.stop();
434 return false;
446 return false;
435 } else {
447 } else {
436 return true;
448 return true;
437 };
449 };
438 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
450 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
439 // If we are not at the bottom, let CM handle the down arrow and
451 // If we are not at the bottom, let CM handle the down arrow and
440 // prevent the global keydown handler from handling it.
452 // prevent the global keydown handler from handling it.
441 if (!that.at_bottom()) {
453 if (!that.at_bottom()) {
442 event.stop();
454 event.stop();
443 return false;
455 return false;
444 } else {
456 } else {
445 return true;
457 return true;
446 };
458 };
447 };
459 };
448 return false;
460 return false;
449 };
461 };
450 return false;
462 return false;
451 };
463 };
452
464
453
465
454 /**
466 /**
455 * @class HeadingCell
467 * @class HeadingCell
456 * @extends IPython.TextCell
468 * @extends IPython.TextCell
457 */
469 */
458
470
459 /**
471 /**
460 * @constructor HeadingCell
472 * @constructor HeadingCell
461 * @extends IPython.TextCell
473 * @extends IPython.TextCell
462 */
474 */
463 var HeadingCell = function (options) {
475 var HeadingCell = function (options) {
464 options = this.mergeopt(HeadingCell, options);
476 options = this.mergeopt(HeadingCell, options);
465
477
466 this.level = 1;
478 this.level = 1;
467 this.cell_type = 'heading';
479 this.cell_type = 'heading';
468 TextCell.apply(this, [options]);
480 TextCell.apply(this, [options]);
469
481
470 /**
482 /**
471 * heading level of the cell, use getter and setter to access
483 * heading level of the cell, use getter and setter to access
472 * @property level
484 * @property level
473 */
485 */
474 };
486 };
475
487
476 HeadingCell.options_default = {
488 HeadingCell.options_default = {
477 placeholder: "Type Heading Here"
489 placeholder: "Type Heading Here"
478 };
490 };
479
491
480 HeadingCell.prototype = new TextCell();
492 HeadingCell.prototype = new TextCell();
481
493
482 /** @method fromJSON */
494 /** @method fromJSON */
483 HeadingCell.prototype.fromJSON = function (data) {
495 HeadingCell.prototype.fromJSON = function (data) {
484 if (data.level != undefined){
496 if (data.level != undefined){
485 this.level = data.level;
497 this.level = data.level;
486 }
498 }
487 TextCell.prototype.fromJSON.apply(this, arguments);
499 TextCell.prototype.fromJSON.apply(this, arguments);
488 };
500 };
489
501
490
502
491 /** @method toJSON */
503 /** @method toJSON */
492 HeadingCell.prototype.toJSON = function () {
504 HeadingCell.prototype.toJSON = function () {
493 var data = TextCell.prototype.toJSON.apply(this);
505 var data = TextCell.prototype.toJSON.apply(this);
494 data.level = this.get_level();
506 data.level = this.get_level();
495 return data;
507 return data;
496 };
508 };
497
509
498 /**
510 /**
499 * can the cell be split into two cells
511 * can the cell be split into two cells
500 * @method is_splittable
512 * @method is_splittable
501 **/
513 **/
502 HeadingCell.prototype.is_splittable = function () {
514 HeadingCell.prototype.is_splittable = function () {
503 return false;
515 return false;
504 };
516 };
505
517
506
518
507 /**
519 /**
508 * can the cell be merged with other cells
520 * can the cell be merged with other cells
509 * @method is_mergeable
521 * @method is_mergeable
510 **/
522 **/
511 HeadingCell.prototype.is_mergeable = function () {
523 HeadingCell.prototype.is_mergeable = function () {
512 return false;
524 return false;
513 };
525 };
514
526
515 /**
527 /**
516 * Change heading level of cell, and re-render
528 * Change heading level of cell, and re-render
517 * @method set_level
529 * @method set_level
518 */
530 */
519 HeadingCell.prototype.set_level = function (level) {
531 HeadingCell.prototype.set_level = function (level) {
520 this.level = level;
532 this.level = level;
521 if (this.rendered) {
533 if (this.rendered) {
522 this.rendered = false;
534 this.rendered = false;
523 this.render();
535 this.render();
524 };
536 };
525 };
537 };
526
538
527 /** The depth of header cell, based on html (h1 to h6)
539 /** The depth of header cell, based on html (h1 to h6)
528 * @method get_level
540 * @method get_level
529 * @return {integer} level - for 1 to 6
541 * @return {integer} level - for 1 to 6
530 */
542 */
531 HeadingCell.prototype.get_level = function () {
543 HeadingCell.prototype.get_level = function () {
532 return this.level;
544 return this.level;
533 };
545 };
534
546
535
547
536 HeadingCell.prototype.set_rendered = function (html) {
548 HeadingCell.prototype.set_rendered = function (html) {
537 this.element.find("div.text_cell_render").html(html);
549 this.element.find("div.text_cell_render").html(html);
538 };
550 };
539
551
540
552
541 HeadingCell.prototype.get_rendered = function () {
553 HeadingCell.prototype.get_rendered = function () {
542 var r = this.element.find("div.text_cell_render");
554 var r = this.element.find("div.text_cell_render");
543 return r.children().first().html();
555 return r.children().first().html();
544 };
556 };
545
557
546
558
547 HeadingCell.prototype.render = function () {
559 HeadingCell.prototype.render = function () {
548 var cont = IPython.TextCell.prototype.render.apply(this);
560 var cont = IPython.TextCell.prototype.render.apply(this);
549 if (cont) {
561 if (cont) {
550 var text = this.get_text();
562 var text = this.get_text();
551 var math = null;
563 var math = null;
552 // Markdown headings must be a single line
564 // Markdown headings must be a single line
553 text = text.replace(/\n/g, ' ');
565 text = text.replace(/\n/g, ' ');
554 if (text === "") { text = this.placeholder; }
566 if (text === "") { text = this.placeholder; }
555 text = Array(this.level + 1).join("#") + " " + text;
567 text = Array(this.level + 1).join("#") + " " + text;
556 var text_and_math = IPython.mathjaxutils.remove_math(text);
568 var text_and_math = IPython.mathjaxutils.remove_math(text);
557 text = text_and_math[0];
569 text = text_and_math[0];
558 math = text_and_math[1];
570 math = text_and_math[1];
559 var html = marked.parser(marked.lexer(text));
571 var html = marked.parser(marked.lexer(text));
560 var h = $(IPython.mathjaxutils.replace_math(html, math));
572 var h = $(IPython.mathjaxutils.replace_math(html, math));
561 // add id and linkback anchor
573 // add id and linkback anchor
562 var hash = h.text().replace(/ /g, '-');
574 var hash = h.text().replace(/ /g, '-');
563 h.attr('id', hash);
575 h.attr('id', hash);
564 h.append(
576 h.append(
565 $('<a/>')
577 $('<a/>')
566 .addClass('anchor-link')
578 .addClass('anchor-link')
567 .attr('href', '#' + hash)
579 .attr('href', '#' + hash)
568 .text('¶')
580 .text('¶')
569 );
581 );
570
582
571 this.set_rendered(h);
583 this.set_rendered(h);
572 this.typeset();
584 this.typeset();
573 this.element.find('div.text_cell_input').hide();
585 this.element.find('div.text_cell_input').hide();
574 this.element.find("div.text_cell_render").show();
586 this.element.find("div.text_cell_render").show();
575
587
576 };
588 };
577 return cont;
589 return cont;
578 };
590 };
579
591
580 IPython.TextCell = TextCell;
592 IPython.TextCell = TextCell;
581 IPython.MarkdownCell = MarkdownCell;
593 IPython.MarkdownCell = MarkdownCell;
582 IPython.RawCell = RawCell;
594 IPython.RawCell = RawCell;
583 IPython.HeadingCell = HeadingCell;
595 IPython.HeadingCell = HeadingCell;
584
596
585
597
586 return IPython;
598 return IPython;
587
599
588 }(IPython));
600 }(IPython));
589
601
General Comments 0
You need to be logged in to leave comments. Login now