##// END OF EJS Templates
Merge pull request #7565 from minrk/missing-focusout...
Matthias Bussonnier -
r20137:342cef63 merge
parent child Browse files
Show More
@@ -1,645 +1,653 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3 /**
3 /**
4 *
4 *
5 *
5 *
6 * @module codecell
6 * @module codecell
7 * @namespace codecell
7 * @namespace codecell
8 * @class CodeCell
8 * @class CodeCell
9 */
9 */
10
10
11
11
12 define([
12 define([
13 'base/js/namespace',
13 'base/js/namespace',
14 'jquery',
14 'jquery',
15 'base/js/utils',
15 'base/js/utils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'services/config',
17 'services/config',
18 'notebook/js/cell',
18 'notebook/js/cell',
19 'notebook/js/outputarea',
19 'notebook/js/outputarea',
20 'notebook/js/completer',
20 'notebook/js/completer',
21 'notebook/js/celltoolbar',
21 'notebook/js/celltoolbar',
22 'codemirror/lib/codemirror',
22 'codemirror/lib/codemirror',
23 'codemirror/mode/python/python',
23 'codemirror/mode/python/python',
24 'notebook/js/codemirror-ipython'
24 'notebook/js/codemirror-ipython'
25 ], function(IPython,
25 ], function(IPython,
26 $,
26 $,
27 utils,
27 utils,
28 keyboard,
28 keyboard,
29 configmod,
29 configmod,
30 cell,
30 cell,
31 outputarea,
31 outputarea,
32 completer,
32 completer,
33 celltoolbar,
33 celltoolbar,
34 CodeMirror,
34 CodeMirror,
35 cmpython,
35 cmpython,
36 cmip
36 cmip
37 ) {
37 ) {
38 "use strict";
38 "use strict";
39
39
40 var Cell = cell.Cell;
40 var Cell = cell.Cell;
41
41
42 /* local util for codemirror */
42 /* local util for codemirror */
43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
44
44
45 /**
45 /**
46 *
46 *
47 * function to delete until previous non blanking space character
47 * function to delete until previous non blanking space character
48 * or first multiple of 4 tabstop.
48 * or first multiple of 4 tabstop.
49 * @private
49 * @private
50 */
50 */
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
55 var tabsize = cm.getOption('tabSize');
55 var tabsize = cm.getOption('tabSize');
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
58 var select = cm.getRange(from,cur);
58 var select = cm.getRange(from,cur);
59 if( select.match(/^\ +$/) !== null){
59 if( select.match(/^\ +$/) !== null){
60 cm.replaceRange("",from,cur);
60 cm.replaceRange("",from,cur);
61 } else {
61 } else {
62 cm.deleteH(-1,"char");
62 cm.deleteH(-1,"char");
63 }
63 }
64 };
64 };
65
65
66 var keycodes = keyboard.keycodes;
66 var keycodes = keyboard.keycodes;
67
67
68 var CodeCell = function (kernel, options) {
68 var CodeCell = function (kernel, options) {
69 /**
69 /**
70 * Constructor
70 * Constructor
71 *
71 *
72 * A Cell conceived to write code.
72 * A Cell conceived to write code.
73 *
73 *
74 * Parameters:
74 * Parameters:
75 * kernel: Kernel instance
75 * kernel: Kernel instance
76 * The kernel doesn't have to be set at creation time, in that case
76 * The kernel doesn't have to be set at creation time, in that case
77 * it will be null and set_kernel has to be called later.
77 * it will be null and set_kernel has to be called later.
78 * options: dictionary
78 * options: dictionary
79 * Dictionary of keyword arguments.
79 * Dictionary of keyword arguments.
80 * events: $(Events) instance
80 * events: $(Events) instance
81 * config: dictionary
81 * config: dictionary
82 * keyboard_manager: KeyboardManager instance
82 * keyboard_manager: KeyboardManager instance
83 * notebook: Notebook instance
83 * notebook: Notebook instance
84 * tooltip: Tooltip instance
84 * tooltip: Tooltip instance
85 */
85 */
86 this.kernel = kernel || null;
86 this.kernel = kernel || null;
87 this.notebook = options.notebook;
87 this.notebook = options.notebook;
88 this.collapsed = false;
88 this.collapsed = false;
89 this.events = options.events;
89 this.events = options.events;
90 this.tooltip = options.tooltip;
90 this.tooltip = options.tooltip;
91 this.config = options.config;
91 this.config = options.config;
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
93 CodeCell.config_defaults, 'CodeCell');
93 CodeCell.config_defaults, 'CodeCell');
94
94
95 // create all attributed in constructor function
95 // create all attributed in constructor function
96 // even if null for V8 VM optimisation
96 // even if null for V8 VM optimisation
97 this.input_prompt_number = null;
97 this.input_prompt_number = null;
98 this.celltoolbar = null;
98 this.celltoolbar = null;
99 this.output_area = null;
99 this.output_area = null;
100
100
101 this.last_msg_id = null;
101 this.last_msg_id = null;
102 this.completer = null;
102 this.completer = null;
103 this.widget_views = [];
103 this.widget_views = [];
104 this._widgets_live = true;
104 this._widgets_live = true;
105
105
106 Cell.apply(this,[{
106 Cell.apply(this,[{
107 config: $.extend({}, CodeCell.options_default),
107 config: $.extend({}, CodeCell.options_default),
108 keyboard_manager: options.keyboard_manager,
108 keyboard_manager: options.keyboard_manager,
109 events: this.events}]);
109 events: this.events}]);
110
110
111 // Attributes we want to override in this subclass.
111 // Attributes we want to override in this subclass.
112 this.cell_type = "code";
112 this.cell_type = "code";
113 var that = this;
113 var that = this;
114 this.element.focusout(
114 this.element.focusout(
115 function() { that.auto_highlight(); }
115 function() { that.auto_highlight(); }
116 );
116 );
117 };
117 };
118
118
119 CodeCell.options_default = {
119 CodeCell.options_default = {
120 cm_config : {
120 cm_config : {
121 extraKeys: {
121 extraKeys: {
122 "Tab" : "indentMore",
122 "Tab" : "indentMore",
123 "Shift-Tab" : "indentLess",
123 "Shift-Tab" : "indentLess",
124 "Backspace" : "delSpaceToPrevTabStop",
124 "Backspace" : "delSpaceToPrevTabStop",
125 "Cmd-/" : "toggleComment",
125 "Cmd-/" : "toggleComment",
126 "Ctrl-/" : "toggleComment"
126 "Ctrl-/" : "toggleComment"
127 },
127 },
128 mode: 'ipython',
128 mode: 'ipython',
129 theme: 'ipython',
129 theme: 'ipython',
130 matchBrackets: true
130 matchBrackets: true
131 }
131 }
132 };
132 };
133
133
134 CodeCell.config_defaults = {
134 CodeCell.config_defaults = {
135 highlight_modes : {
135 highlight_modes : {
136 'magic_javascript' :{'reg':[/^%%javascript/]},
136 'magic_javascript' :{'reg':[/^%%javascript/]},
137 'magic_perl' :{'reg':[/^%%perl/]},
137 'magic_perl' :{'reg':[/^%%perl/]},
138 'magic_ruby' :{'reg':[/^%%ruby/]},
138 'magic_ruby' :{'reg':[/^%%ruby/]},
139 'magic_python' :{'reg':[/^%%python3?/]},
139 'magic_python' :{'reg':[/^%%python3?/]},
140 'magic_shell' :{'reg':[/^%%bash/]},
140 'magic_shell' :{'reg':[/^%%bash/]},
141 'magic_r' :{'reg':[/^%%R/]},
141 'magic_r' :{'reg':[/^%%R/]},
142 'magic_text/x-cython' :{'reg':[/^%%cython/]},
142 'magic_text/x-cython' :{'reg':[/^%%cython/]},
143 },
143 },
144 };
144 };
145
145
146 CodeCell.msg_cells = {};
146 CodeCell.msg_cells = {};
147
147
148 CodeCell.prototype = Object.create(Cell.prototype);
148 CodeCell.prototype = Object.create(Cell.prototype);
149
149
150 /** @method create_element */
150 /** @method create_element */
151 CodeCell.prototype.create_element = function () {
151 CodeCell.prototype.create_element = function () {
152 Cell.prototype.create_element.apply(this, arguments);
152 Cell.prototype.create_element.apply(this, arguments);
153 var that = this;
153
154
154 var cell = $('<div></div>').addClass('cell code_cell');
155 var cell = $('<div></div>').addClass('cell code_cell');
155 cell.attr('tabindex','2');
156 cell.attr('tabindex','2');
156
157
157 var input = $('<div></div>').addClass('input');
158 var input = $('<div></div>').addClass('input');
158 var prompt = $('<div/>').addClass('prompt input_prompt');
159 var prompt = $('<div/>').addClass('prompt input_prompt');
159 var inner_cell = $('<div/>').addClass('inner_cell');
160 var inner_cell = $('<div/>').addClass('inner_cell');
160 this.celltoolbar = new celltoolbar.CellToolbar({
161 this.celltoolbar = new celltoolbar.CellToolbar({
161 cell: this,
162 cell: this,
162 notebook: this.notebook});
163 notebook: this.notebook});
163 inner_cell.append(this.celltoolbar.element);
164 inner_cell.append(this.celltoolbar.element);
164 var input_area = $('<div/>').addClass('input_area');
165 var input_area = $('<div/>').addClass('input_area');
165 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
166 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
167 // In case of bugs that put the keyboard manager into an inconsistent state,
168 // ensure KM is enabled when CodeMirror is focused:
169 this.code_mirror.on('focus', function () {
170 if (that.keyboard_manager) {
171 that.keyboard_manager.enable();
172 }
173 });
166 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
174 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
167 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
175 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
168 inner_cell.append(input_area);
176 inner_cell.append(input_area);
169 input.append(prompt).append(inner_cell);
177 input.append(prompt).append(inner_cell);
170
178
171 var widget_area = $('<div/>')
179 var widget_area = $('<div/>')
172 .addClass('widget-area')
180 .addClass('widget-area')
173 .hide();
181 .hide();
174 this.widget_area = widget_area;
182 this.widget_area = widget_area;
175 var widget_prompt = $('<div/>')
183 var widget_prompt = $('<div/>')
176 .addClass('prompt')
184 .addClass('prompt')
177 .appendTo(widget_area);
185 .appendTo(widget_area);
178 var widget_subarea = $('<div/>')
186 var widget_subarea = $('<div/>')
179 .addClass('widget-subarea')
187 .addClass('widget-subarea')
180 .appendTo(widget_area);
188 .appendTo(widget_area);
181 this.widget_subarea = widget_subarea;
189 this.widget_subarea = widget_subarea;
182 var that = this;
190 var that = this;
183 var widget_clear_buton = $('<button />')
191 var widget_clear_buton = $('<button />')
184 .addClass('close')
192 .addClass('close')
185 .html('&times;')
193 .html('&times;')
186 .click(function() {
194 .click(function() {
187 widget_area.slideUp('', function(){
195 widget_area.slideUp('', function(){
188 for (var i = 0; i < that.widget_views.length; i++) {
196 for (var i = 0; i < that.widget_views.length; i++) {
189 var view = that.widget_views[i];
197 var view = that.widget_views[i];
190 view.remove();
198 view.remove();
191
199
192 // Remove widget live events.
200 // Remove widget live events.
193 view.off('comm:live', that._widget_live);
201 view.off('comm:live', that._widget_live);
194 view.off('comm:dead', that._widget_dead);
202 view.off('comm:dead', that._widget_dead);
195 }
203 }
196 that.widget_views = [];
204 that.widget_views = [];
197 widget_subarea.html('');
205 widget_subarea.html('');
198 });
206 });
199 })
207 })
200 .appendTo(widget_prompt);
208 .appendTo(widget_prompt);
201
209
202 var output = $('<div></div>');
210 var output = $('<div></div>');
203 cell.append(input).append(widget_area).append(output);
211 cell.append(input).append(widget_area).append(output);
204 this.element = cell;
212 this.element = cell;
205 this.output_area = new outputarea.OutputArea({
213 this.output_area = new outputarea.OutputArea({
206 selector: output,
214 selector: output,
207 prompt_area: true,
215 prompt_area: true,
208 events: this.events,
216 events: this.events,
209 keyboard_manager: this.keyboard_manager});
217 keyboard_manager: this.keyboard_manager});
210 this.completer = new completer.Completer(this, this.events);
218 this.completer = new completer.Completer(this, this.events);
211 };
219 };
212
220
213 /**
221 /**
214 * Display a widget view in the cell.
222 * Display a widget view in the cell.
215 */
223 */
216 CodeCell.prototype.display_widget_view = function(view_promise) {
224 CodeCell.prototype.display_widget_view = function(view_promise) {
217
225
218 // Display a dummy element
226 // Display a dummy element
219 var dummy = $('<div/>');
227 var dummy = $('<div/>');
220 this.widget_subarea.append(dummy);
228 this.widget_subarea.append(dummy);
221
229
222 // Display the view.
230 // Display the view.
223 var that = this;
231 var that = this;
224 return view_promise.then(function(view) {
232 return view_promise.then(function(view) {
225 that.widget_area.show();
233 that.widget_area.show();
226 dummy.replaceWith(view.$el);
234 dummy.replaceWith(view.$el);
227 that.widget_views.push(view);
235 that.widget_views.push(view);
228
236
229 // Check the live state of the view's model.
237 // Check the live state of the view's model.
230 if (view.model.comm_live) {
238 if (view.model.comm_live) {
231 that._widget_live(view);
239 that._widget_live(view);
232 } else {
240 } else {
233 that._widget_dead(view);
241 that._widget_dead(view);
234 }
242 }
235
243
236 // Listen to comm live events for the view.
244 // Listen to comm live events for the view.
237 view.on('comm:live', that._widget_live, that);
245 view.on('comm:live', that._widget_live, that);
238 view.on('comm:dead', that._widget_dead, that);
246 view.on('comm:dead', that._widget_dead, that);
239 return view;
247 return view;
240 });
248 });
241 };
249 };
242
250
243 /**
251 /**
244 * Handles when a widget loses it's comm connection.
252 * Handles when a widget loses it's comm connection.
245 * @param {WidgetView} view
253 * @param {WidgetView} view
246 */
254 */
247 CodeCell.prototype._widget_dead = function(view) {
255 CodeCell.prototype._widget_dead = function(view) {
248 if (this._widgets_live) {
256 if (this._widgets_live) {
249 this._widgets_live = false;
257 this._widgets_live = false;
250 this.widget_area.addClass('connection-problems');
258 this.widget_area.addClass('connection-problems');
251 }
259 }
252
260
253 };
261 };
254
262
255 /**
263 /**
256 * Handles when a widget is connected to a live comm.
264 * Handles when a widget is connected to a live comm.
257 * @param {WidgetView} view
265 * @param {WidgetView} view
258 */
266 */
259 CodeCell.prototype._widget_live = function(view) {
267 CodeCell.prototype._widget_live = function(view) {
260 if (!this._widgets_live) {
268 if (!this._widgets_live) {
261 // Check that the other widgets are live too. O(N) operation.
269 // Check that the other widgets are live too. O(N) operation.
262 // Abort the function at the first dead widget found.
270 // Abort the function at the first dead widget found.
263 for (var i = 0; i < this.widget_views.length; i++) {
271 for (var i = 0; i < this.widget_views.length; i++) {
264 if (!this.widget_views[i].model.comm_live) return;
272 if (!this.widget_views[i].model.comm_live) return;
265 }
273 }
266 this._widgets_live = true;
274 this._widgets_live = true;
267 this.widget_area.removeClass('connection-problems');
275 this.widget_area.removeClass('connection-problems');
268 }
276 }
269 };
277 };
270
278
271 /** @method bind_events */
279 /** @method bind_events */
272 CodeCell.prototype.bind_events = function () {
280 CodeCell.prototype.bind_events = function () {
273 Cell.prototype.bind_events.apply(this);
281 Cell.prototype.bind_events.apply(this);
274 var that = this;
282 var that = this;
275
283
276 this.element.focusout(
284 this.element.focusout(
277 function() { that.auto_highlight(); }
285 function() { that.auto_highlight(); }
278 );
286 );
279 };
287 };
280
288
281
289
282 /**
290 /**
283 * This method gets called in CodeMirror's onKeyDown/onKeyPress
291 * This method gets called in CodeMirror's onKeyDown/onKeyPress
284 * handlers and is used to provide custom key handling. Its return
292 * handlers and is used to provide custom key handling. Its return
285 * value is used to determine if CodeMirror should ignore the event:
293 * value is used to determine if CodeMirror should ignore the event:
286 * true = ignore, false = don't ignore.
294 * true = ignore, false = don't ignore.
287 * @method handle_codemirror_keyevent
295 * @method handle_codemirror_keyevent
288 */
296 */
289
297
290 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
298 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
291
299
292 var that = this;
300 var that = this;
293 // whatever key is pressed, first, cancel the tooltip request before
301 // whatever key is pressed, first, cancel the tooltip request before
294 // they are sent, and remove tooltip if any, except for tab again
302 // they are sent, and remove tooltip if any, except for tab again
295 var tooltip_closed = null;
303 var tooltip_closed = null;
296 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
304 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
297 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
305 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
298 }
306 }
299
307
300 var cur = editor.getCursor();
308 var cur = editor.getCursor();
301 if (event.keyCode === keycodes.enter){
309 if (event.keyCode === keycodes.enter){
302 this.auto_highlight();
310 this.auto_highlight();
303 }
311 }
304
312
305 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
313 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
306 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
314 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
307 // browser and keyboard layout !
315 // browser and keyboard layout !
308 // Pressing '(' , request tooltip, don't forget to reappend it
316 // Pressing '(' , request tooltip, don't forget to reappend it
309 // The second argument says to hide the tooltip if the docstring
317 // The second argument says to hide the tooltip if the docstring
310 // is actually empty
318 // is actually empty
311 this.tooltip.pending(that, true);
319 this.tooltip.pending(that, true);
312 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
320 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
313 // If tooltip is active, cancel it. The call to
321 // If tooltip is active, cancel it. The call to
314 // remove_and_cancel_tooltip above doesn't pass, force=true.
322 // remove_and_cancel_tooltip above doesn't pass, force=true.
315 // Because of this it won't actually close the tooltip
323 // Because of this it won't actually close the tooltip
316 // if it is in sticky mode. Thus, we have to check again if it is open
324 // if it is in sticky mode. Thus, we have to check again if it is open
317 // and close it with force=true.
325 // and close it with force=true.
318 if (!this.tooltip._hidden) {
326 if (!this.tooltip._hidden) {
319 this.tooltip.remove_and_cancel_tooltip(true);
327 this.tooltip.remove_and_cancel_tooltip(true);
320 }
328 }
321 // If we closed the tooltip, don't let CM or the global handlers
329 // If we closed the tooltip, don't let CM or the global handlers
322 // handle this event.
330 // handle this event.
323 event.codemirrorIgnore = true;
331 event.codemirrorIgnore = true;
324 event.preventDefault();
332 event.preventDefault();
325 return true;
333 return true;
326 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
334 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
327 if (editor.somethingSelected() || editor.getSelections().length !== 1){
335 if (editor.somethingSelected() || editor.getSelections().length !== 1){
328 var anchor = editor.getCursor("anchor");
336 var anchor = editor.getCursor("anchor");
329 var head = editor.getCursor("head");
337 var head = editor.getCursor("head");
330 if( anchor.line !== head.line){
338 if( anchor.line !== head.line){
331 return false;
339 return false;
332 }
340 }
333 }
341 }
334 this.tooltip.request(that);
342 this.tooltip.request(that);
335 event.codemirrorIgnore = true;
343 event.codemirrorIgnore = true;
336 event.preventDefault();
344 event.preventDefault();
337 return true;
345 return true;
338 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
346 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
339 // Tab completion.
347 // Tab completion.
340 this.tooltip.remove_and_cancel_tooltip();
348 this.tooltip.remove_and_cancel_tooltip();
341
349
342 // completion does not work on multicursor, it might be possible though in some cases
350 // completion does not work on multicursor, it might be possible though in some cases
343 if (editor.somethingSelected() || editor.getSelections().length > 1) {
351 if (editor.somethingSelected() || editor.getSelections().length > 1) {
344 return false;
352 return false;
345 }
353 }
346 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
354 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
347 if (pre_cursor.trim() === "") {
355 if (pre_cursor.trim() === "") {
348 // Don't autocomplete if the part of the line before the cursor
356 // Don't autocomplete if the part of the line before the cursor
349 // is empty. In this case, let CodeMirror handle indentation.
357 // is empty. In this case, let CodeMirror handle indentation.
350 return false;
358 return false;
351 } else {
359 } else {
352 event.codemirrorIgnore = true;
360 event.codemirrorIgnore = true;
353 event.preventDefault();
361 event.preventDefault();
354 this.completer.startCompletion();
362 this.completer.startCompletion();
355 return true;
363 return true;
356 }
364 }
357 }
365 }
358
366
359 // keyboard event wasn't one of those unique to code cells, let's see
367 // keyboard event wasn't one of those unique to code cells, let's see
360 // if it's one of the generic ones (i.e. check edit mode shortcuts)
368 // if it's one of the generic ones (i.e. check edit mode shortcuts)
361 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
369 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
362 };
370 };
363
371
364 // Kernel related calls.
372 // Kernel related calls.
365
373
366 CodeCell.prototype.set_kernel = function (kernel) {
374 CodeCell.prototype.set_kernel = function (kernel) {
367 this.kernel = kernel;
375 this.kernel = kernel;
368 };
376 };
369
377
370 /**
378 /**
371 * Execute current code cell to the kernel
379 * Execute current code cell to the kernel
372 * @method execute
380 * @method execute
373 */
381 */
374 CodeCell.prototype.execute = function (stop_on_error) {
382 CodeCell.prototype.execute = function (stop_on_error) {
375 if (!this.kernel || !this.kernel.is_connected()) {
383 if (!this.kernel || !this.kernel.is_connected()) {
376 console.log("Can't execute, kernel is not connected.");
384 console.log("Can't execute, kernel is not connected.");
377 return;
385 return;
378 }
386 }
379
387
380 this.output_area.clear_output(false, true);
388 this.output_area.clear_output(false, true);
381
389
382 if (stop_on_error === undefined) {
390 if (stop_on_error === undefined) {
383 stop_on_error = true;
391 stop_on_error = true;
384 }
392 }
385
393
386 // Clear widget area
394 // Clear widget area
387 for (var i = 0; i < this.widget_views.length; i++) {
395 for (var i = 0; i < this.widget_views.length; i++) {
388 var view = this.widget_views[i];
396 var view = this.widget_views[i];
389 view.remove();
397 view.remove();
390
398
391 // Remove widget live events.
399 // Remove widget live events.
392 view.off('comm:live', this._widget_live);
400 view.off('comm:live', this._widget_live);
393 view.off('comm:dead', this._widget_dead);
401 view.off('comm:dead', this._widget_dead);
394 }
402 }
395 this.widget_views = [];
403 this.widget_views = [];
396 this.widget_subarea.html('');
404 this.widget_subarea.html('');
397 this.widget_subarea.height('');
405 this.widget_subarea.height('');
398 this.widget_area.height('');
406 this.widget_area.height('');
399 this.widget_area.hide();
407 this.widget_area.hide();
400
408
401 this.set_input_prompt('*');
409 this.set_input_prompt('*');
402 this.element.addClass("running");
410 this.element.addClass("running");
403 if (this.last_msg_id) {
411 if (this.last_msg_id) {
404 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
412 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
405 }
413 }
406 var callbacks = this.get_callbacks();
414 var callbacks = this.get_callbacks();
407
415
408 var old_msg_id = this.last_msg_id;
416 var old_msg_id = this.last_msg_id;
409 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
417 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
410 stop_on_error : stop_on_error});
418 stop_on_error : stop_on_error});
411 if (old_msg_id) {
419 if (old_msg_id) {
412 delete CodeCell.msg_cells[old_msg_id];
420 delete CodeCell.msg_cells[old_msg_id];
413 }
421 }
414 CodeCell.msg_cells[this.last_msg_id] = this;
422 CodeCell.msg_cells[this.last_msg_id] = this;
415 this.render();
423 this.render();
416 this.events.trigger('execute.CodeCell', {cell: this});
424 this.events.trigger('execute.CodeCell', {cell: this});
417 };
425 };
418
426
419 /**
427 /**
420 * Construct the default callbacks for
428 * Construct the default callbacks for
421 * @method get_callbacks
429 * @method get_callbacks
422 */
430 */
423 CodeCell.prototype.get_callbacks = function () {
431 CodeCell.prototype.get_callbacks = function () {
424 var that = this;
432 var that = this;
425 return {
433 return {
426 shell : {
434 shell : {
427 reply : $.proxy(this._handle_execute_reply, this),
435 reply : $.proxy(this._handle_execute_reply, this),
428 payload : {
436 payload : {
429 set_next_input : $.proxy(this._handle_set_next_input, this),
437 set_next_input : $.proxy(this._handle_set_next_input, this),
430 page : $.proxy(this._open_with_pager, this)
438 page : $.proxy(this._open_with_pager, this)
431 }
439 }
432 },
440 },
433 iopub : {
441 iopub : {
434 output : function() {
442 output : function() {
435 that.output_area.handle_output.apply(that.output_area, arguments);
443 that.output_area.handle_output.apply(that.output_area, arguments);
436 },
444 },
437 clear_output : function() {
445 clear_output : function() {
438 that.output_area.handle_clear_output.apply(that.output_area, arguments);
446 that.output_area.handle_clear_output.apply(that.output_area, arguments);
439 },
447 },
440 },
448 },
441 input : $.proxy(this._handle_input_request, this)
449 input : $.proxy(this._handle_input_request, this)
442 };
450 };
443 };
451 };
444
452
445 CodeCell.prototype._open_with_pager = function (payload) {
453 CodeCell.prototype._open_with_pager = function (payload) {
446 this.events.trigger('open_with_text.Pager', payload);
454 this.events.trigger('open_with_text.Pager', payload);
447 };
455 };
448
456
449 /**
457 /**
450 * @method _handle_execute_reply
458 * @method _handle_execute_reply
451 * @private
459 * @private
452 */
460 */
453 CodeCell.prototype._handle_execute_reply = function (msg) {
461 CodeCell.prototype._handle_execute_reply = function (msg) {
454 this.set_input_prompt(msg.content.execution_count);
462 this.set_input_prompt(msg.content.execution_count);
455 this.element.removeClass("running");
463 this.element.removeClass("running");
456 this.events.trigger('set_dirty.Notebook', {value: true});
464 this.events.trigger('set_dirty.Notebook', {value: true});
457 };
465 };
458
466
459 /**
467 /**
460 * @method _handle_set_next_input
468 * @method _handle_set_next_input
461 * @private
469 * @private
462 */
470 */
463 CodeCell.prototype._handle_set_next_input = function (payload) {
471 CodeCell.prototype._handle_set_next_input = function (payload) {
464 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
472 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
465 this.events.trigger('set_next_input.Notebook', data);
473 this.events.trigger('set_next_input.Notebook', data);
466 };
474 };
467
475
468 /**
476 /**
469 * @method _handle_input_request
477 * @method _handle_input_request
470 * @private
478 * @private
471 */
479 */
472 CodeCell.prototype._handle_input_request = function (msg) {
480 CodeCell.prototype._handle_input_request = function (msg) {
473 this.output_area.append_raw_input(msg);
481 this.output_area.append_raw_input(msg);
474 };
482 };
475
483
476
484
477 // Basic cell manipulation.
485 // Basic cell manipulation.
478
486
479 CodeCell.prototype.select = function () {
487 CodeCell.prototype.select = function () {
480 var cont = Cell.prototype.select.apply(this);
488 var cont = Cell.prototype.select.apply(this);
481 if (cont) {
489 if (cont) {
482 this.code_mirror.refresh();
490 this.code_mirror.refresh();
483 this.auto_highlight();
491 this.auto_highlight();
484 }
492 }
485 return cont;
493 return cont;
486 };
494 };
487
495
488 CodeCell.prototype.render = function () {
496 CodeCell.prototype.render = function () {
489 var cont = Cell.prototype.render.apply(this);
497 var cont = Cell.prototype.render.apply(this);
490 // Always execute, even if we are already in the rendered state
498 // Always execute, even if we are already in the rendered state
491 return cont;
499 return cont;
492 };
500 };
493
501
494 CodeCell.prototype.select_all = function () {
502 CodeCell.prototype.select_all = function () {
495 var start = {line: 0, ch: 0};
503 var start = {line: 0, ch: 0};
496 var nlines = this.code_mirror.lineCount();
504 var nlines = this.code_mirror.lineCount();
497 var last_line = this.code_mirror.getLine(nlines-1);
505 var last_line = this.code_mirror.getLine(nlines-1);
498 var end = {line: nlines-1, ch: last_line.length};
506 var end = {line: nlines-1, ch: last_line.length};
499 this.code_mirror.setSelection(start, end);
507 this.code_mirror.setSelection(start, end);
500 };
508 };
501
509
502
510
503 CodeCell.prototype.collapse_output = function () {
511 CodeCell.prototype.collapse_output = function () {
504 this.output_area.collapse();
512 this.output_area.collapse();
505 };
513 };
506
514
507
515
508 CodeCell.prototype.expand_output = function () {
516 CodeCell.prototype.expand_output = function () {
509 this.output_area.expand();
517 this.output_area.expand();
510 this.output_area.unscroll_area();
518 this.output_area.unscroll_area();
511 };
519 };
512
520
513 CodeCell.prototype.scroll_output = function () {
521 CodeCell.prototype.scroll_output = function () {
514 this.output_area.expand();
522 this.output_area.expand();
515 this.output_area.scroll_if_long();
523 this.output_area.scroll_if_long();
516 };
524 };
517
525
518 CodeCell.prototype.toggle_output = function () {
526 CodeCell.prototype.toggle_output = function () {
519 this.output_area.toggle_output();
527 this.output_area.toggle_output();
520 };
528 };
521
529
522 CodeCell.prototype.toggle_output_scroll = function () {
530 CodeCell.prototype.toggle_output_scroll = function () {
523 this.output_area.toggle_scroll();
531 this.output_area.toggle_scroll();
524 };
532 };
525
533
526
534
527 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
535 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
528 var ns;
536 var ns;
529 if (prompt_value === undefined || prompt_value === null) {
537 if (prompt_value === undefined || prompt_value === null) {
530 ns = "&nbsp;";
538 ns = "&nbsp;";
531 } else {
539 } else {
532 ns = encodeURIComponent(prompt_value);
540 ns = encodeURIComponent(prompt_value);
533 }
541 }
534 return 'In&nbsp;[' + ns + ']:';
542 return 'In&nbsp;[' + ns + ']:';
535 };
543 };
536
544
537 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
545 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
538 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
546 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
539 for(var i=1; i < lines_number; i++) {
547 for(var i=1; i < lines_number; i++) {
540 html.push(['...:']);
548 html.push(['...:']);
541 }
549 }
542 return html.join('<br/>');
550 return html.join('<br/>');
543 };
551 };
544
552
545 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
553 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
546
554
547
555
548 CodeCell.prototype.set_input_prompt = function (number) {
556 CodeCell.prototype.set_input_prompt = function (number) {
549 var nline = 1;
557 var nline = 1;
550 if (this.code_mirror !== undefined) {
558 if (this.code_mirror !== undefined) {
551 nline = this.code_mirror.lineCount();
559 nline = this.code_mirror.lineCount();
552 }
560 }
553 this.input_prompt_number = number;
561 this.input_prompt_number = number;
554 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
562 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
555 // This HTML call is okay because the user contents are escaped.
563 // This HTML call is okay because the user contents are escaped.
556 this.element.find('div.input_prompt').html(prompt_html);
564 this.element.find('div.input_prompt').html(prompt_html);
557 };
565 };
558
566
559
567
560 CodeCell.prototype.clear_input = function () {
568 CodeCell.prototype.clear_input = function () {
561 this.code_mirror.setValue('');
569 this.code_mirror.setValue('');
562 };
570 };
563
571
564
572
565 CodeCell.prototype.get_text = function () {
573 CodeCell.prototype.get_text = function () {
566 return this.code_mirror.getValue();
574 return this.code_mirror.getValue();
567 };
575 };
568
576
569
577
570 CodeCell.prototype.set_text = function (code) {
578 CodeCell.prototype.set_text = function (code) {
571 return this.code_mirror.setValue(code);
579 return this.code_mirror.setValue(code);
572 };
580 };
573
581
574
582
575 CodeCell.prototype.clear_output = function (wait) {
583 CodeCell.prototype.clear_output = function (wait) {
576 this.output_area.clear_output(wait);
584 this.output_area.clear_output(wait);
577 this.set_input_prompt();
585 this.set_input_prompt();
578 };
586 };
579
587
580
588
581 // JSON serialization
589 // JSON serialization
582
590
583 CodeCell.prototype.fromJSON = function (data) {
591 CodeCell.prototype.fromJSON = function (data) {
584 Cell.prototype.fromJSON.apply(this, arguments);
592 Cell.prototype.fromJSON.apply(this, arguments);
585 if (data.cell_type === 'code') {
593 if (data.cell_type === 'code') {
586 if (data.source !== undefined) {
594 if (data.source !== undefined) {
587 this.set_text(data.source);
595 this.set_text(data.source);
588 // make this value the starting point, so that we can only undo
596 // make this value the starting point, so that we can only undo
589 // to this state, instead of a blank cell
597 // to this state, instead of a blank cell
590 this.code_mirror.clearHistory();
598 this.code_mirror.clearHistory();
591 this.auto_highlight();
599 this.auto_highlight();
592 }
600 }
593 this.set_input_prompt(data.execution_count);
601 this.set_input_prompt(data.execution_count);
594 this.output_area.trusted = data.metadata.trusted || false;
602 this.output_area.trusted = data.metadata.trusted || false;
595 this.output_area.fromJSON(data.outputs);
603 this.output_area.fromJSON(data.outputs);
596 if (data.metadata.collapsed !== undefined) {
604 if (data.metadata.collapsed !== undefined) {
597 if (data.metadata.collapsed) {
605 if (data.metadata.collapsed) {
598 this.collapse_output();
606 this.collapse_output();
599 } else {
607 } else {
600 this.expand_output();
608 this.expand_output();
601 }
609 }
602 }
610 }
603 }
611 }
604 };
612 };
605
613
606
614
607 CodeCell.prototype.toJSON = function () {
615 CodeCell.prototype.toJSON = function () {
608 var data = Cell.prototype.toJSON.apply(this);
616 var data = Cell.prototype.toJSON.apply(this);
609 data.source = this.get_text();
617 data.source = this.get_text();
610 // is finite protect against undefined and '*' value
618 // is finite protect against undefined and '*' value
611 if (isFinite(this.input_prompt_number)) {
619 if (isFinite(this.input_prompt_number)) {
612 data.execution_count = this.input_prompt_number;
620 data.execution_count = this.input_prompt_number;
613 } else {
621 } else {
614 data.execution_count = null;
622 data.execution_count = null;
615 }
623 }
616 var outputs = this.output_area.toJSON();
624 var outputs = this.output_area.toJSON();
617 data.outputs = outputs;
625 data.outputs = outputs;
618 data.metadata.trusted = this.output_area.trusted;
626 data.metadata.trusted = this.output_area.trusted;
619 data.metadata.collapsed = this.output_area.collapsed;
627 data.metadata.collapsed = this.output_area.collapsed;
620 return data;
628 return data;
621 };
629 };
622
630
623 /**
631 /**
624 * handle cell level logic when a cell is unselected
632 * handle cell level logic when a cell is unselected
625 * @method unselect
633 * @method unselect
626 * @return is the action being taken
634 * @return is the action being taken
627 */
635 */
628 CodeCell.prototype.unselect = function () {
636 CodeCell.prototype.unselect = function () {
629 var cont = Cell.prototype.unselect.apply(this);
637 var cont = Cell.prototype.unselect.apply(this);
630 if (cont) {
638 if (cont) {
631 // When a code cell is usnelected, make sure that the corresponding
639 // When a code cell is usnelected, make sure that the corresponding
632 // tooltip and completer to that cell is closed.
640 // tooltip and completer to that cell is closed.
633 this.tooltip.remove_and_cancel_tooltip(true);
641 this.tooltip.remove_and_cancel_tooltip(true);
634 if (this.completer !== null) {
642 if (this.completer !== null) {
635 this.completer.close();
643 this.completer.close();
636 }
644 }
637 }
645 }
638 return cont;
646 return cont;
639 };
647 };
640
648
641 // Backwards compatability.
649 // Backwards compatability.
642 IPython.CodeCell = CodeCell;
650 IPython.CodeCell = CodeCell;
643
651
644 return {'CodeCell': CodeCell};
652 return {'CodeCell': CodeCell};
645 });
653 });
@@ -1,367 +1,375 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'base/js/utils',
6 'base/js/utils',
7 'jquery',
7 'jquery',
8 'notebook/js/cell',
8 'notebook/js/cell',
9 'base/js/security',
9 'base/js/security',
10 'services/config',
10 'services/config',
11 'notebook/js/mathjaxutils',
11 'notebook/js/mathjaxutils',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'codemirror/lib/codemirror',
14 'codemirror/lib/codemirror',
15 'codemirror/mode/gfm/gfm',
15 'codemirror/mode/gfm/gfm',
16 'notebook/js/codemirror-ipythongfm'
16 'notebook/js/codemirror-ipythongfm'
17 ], function(IPython,
17 ], function(IPython,
18 utils,
18 utils,
19 $,
19 $,
20 cell,
20 cell,
21 security,
21 security,
22 configmod,
22 configmod,
23 mathjaxutils,
23 mathjaxutils,
24 celltoolbar,
24 celltoolbar,
25 marked,
25 marked,
26 CodeMirror,
26 CodeMirror,
27 gfm,
27 gfm,
28 ipgfm
28 ipgfm
29 ) {
29 ) {
30 "use strict";
30 "use strict";
31 var Cell = cell.Cell;
31 var Cell = cell.Cell;
32
32
33 var TextCell = function (options) {
33 var TextCell = function (options) {
34 /**
34 /**
35 * Constructor
35 * Constructor
36 *
36 *
37 * Construct a new TextCell, codemirror mode is by default 'htmlmixed',
37 * Construct a new TextCell, codemirror mode is by default 'htmlmixed',
38 * and cell type is 'text' cell start as not redered.
38 * and cell type is 'text' cell start as not redered.
39 *
39 *
40 * Parameters:
40 * Parameters:
41 * options: dictionary
41 * options: dictionary
42 * Dictionary of keyword arguments.
42 * Dictionary of keyword arguments.
43 * events: $(Events) instance
43 * events: $(Events) instance
44 * config: dictionary
44 * config: dictionary
45 * keyboard_manager: KeyboardManager instance
45 * keyboard_manager: KeyboardManager instance
46 * notebook: Notebook instance
46 * notebook: Notebook instance
47 */
47 */
48 options = options || {};
48 options = options || {};
49
49
50 // in all TextCell/Cell subclasses
50 // in all TextCell/Cell subclasses
51 // do not assign most of members here, just pass it down
51 // do not assign most of members here, just pass it down
52 // in the options dict potentially overwriting what you wish.
52 // in the options dict potentially overwriting what you wish.
53 // they will be assigned in the base class.
53 // they will be assigned in the base class.
54 this.notebook = options.notebook;
54 this.notebook = options.notebook;
55 this.events = options.events;
55 this.events = options.events;
56 this.config = options.config;
56 this.config = options.config;
57
57
58 // we cannot put this as a class key as it has handle to "this".
58 // we cannot put this as a class key as it has handle to "this".
59 var config = utils.mergeopt(TextCell, this.config);
59 var config = utils.mergeopt(TextCell, this.config);
60 Cell.apply(this, [{
60 Cell.apply(this, [{
61 config: config,
61 config: config,
62 keyboard_manager: options.keyboard_manager,
62 keyboard_manager: options.keyboard_manager,
63 events: this.events}]);
63 events: this.events}]);
64
64
65 this.cell_type = this.cell_type || 'text';
65 this.cell_type = this.cell_type || 'text';
66 mathjaxutils = mathjaxutils;
66 mathjaxutils = mathjaxutils;
67 this.rendered = false;
67 this.rendered = false;
68 };
68 };
69
69
70 TextCell.prototype = Object.create(Cell.prototype);
70 TextCell.prototype = Object.create(Cell.prototype);
71
71
72 TextCell.options_default = {
72 TextCell.options_default = {
73 cm_config : {
73 cm_config : {
74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
75 mode: 'htmlmixed',
75 mode: 'htmlmixed',
76 lineWrapping : true,
76 lineWrapping : true,
77 }
77 }
78 };
78 };
79
79
80
80
81 /**
81 /**
82 * Create the DOM element of the TextCell
82 * Create the DOM element of the TextCell
83 * @method create_element
83 * @method create_element
84 * @private
84 * @private
85 */
85 */
86 TextCell.prototype.create_element = function () {
86 TextCell.prototype.create_element = function () {
87 Cell.prototype.create_element.apply(this, arguments);
87 Cell.prototype.create_element.apply(this, arguments);
88 var that = this;
88
89
89 var cell = $("<div>").addClass('cell text_cell');
90 var cell = $("<div>").addClass('cell text_cell');
90 cell.attr('tabindex','2');
91 cell.attr('tabindex','2');
91
92
92 var prompt = $('<div/>').addClass('prompt input_prompt');
93 var prompt = $('<div/>').addClass('prompt input_prompt');
93 cell.append(prompt);
94 cell.append(prompt);
94 var inner_cell = $('<div/>').addClass('inner_cell');
95 var inner_cell = $('<div/>').addClass('inner_cell');
95 this.celltoolbar = new celltoolbar.CellToolbar({
96 this.celltoolbar = new celltoolbar.CellToolbar({
96 cell: this,
97 cell: this,
97 notebook: this.notebook});
98 notebook: this.notebook});
98 inner_cell.append(this.celltoolbar.element);
99 inner_cell.append(this.celltoolbar.element);
99 var input_area = $('<div/>').addClass('input_area');
100 var input_area = $('<div/>').addClass('input_area');
100 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
101 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
102 // In case of bugs that put the keyboard manager into an inconsistent state,
103 // ensure KM is enabled when CodeMirror is focused:
104 this.code_mirror.on('focus', function () {
105 if (that.keyboard_manager) {
106 that.keyboard_manager.enable();
107 }
108 });
101 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
109 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
102 // The tabindex=-1 makes this div focusable.
110 // The tabindex=-1 makes this div focusable.
103 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
111 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
104 .attr('tabindex','-1');
112 .attr('tabindex','-1');
105 inner_cell.append(input_area).append(render_area);
113 inner_cell.append(input_area).append(render_area);
106 cell.append(inner_cell);
114 cell.append(inner_cell);
107 this.element = cell;
115 this.element = cell;
108 };
116 };
109
117
110
118
111 // Cell level actions
119 // Cell level actions
112
120
113 TextCell.prototype.select = function () {
121 TextCell.prototype.select = function () {
114 var cont = Cell.prototype.select.apply(this);
122 var cont = Cell.prototype.select.apply(this);
115 if (cont) {
123 if (cont) {
116 if (this.mode === 'edit') {
124 if (this.mode === 'edit') {
117 this.code_mirror.refresh();
125 this.code_mirror.refresh();
118 }
126 }
119 }
127 }
120 return cont;
128 return cont;
121 };
129 };
122
130
123 TextCell.prototype.unrender = function () {
131 TextCell.prototype.unrender = function () {
124 if (this.read_only) return;
132 if (this.read_only) return;
125 var cont = Cell.prototype.unrender.apply(this);
133 var cont = Cell.prototype.unrender.apply(this);
126 if (cont) {
134 if (cont) {
127 var text_cell = this.element;
135 var text_cell = this.element;
128 if (this.get_text() === this.placeholder) {
136 if (this.get_text() === this.placeholder) {
129 this.set_text('');
137 this.set_text('');
130 }
138 }
131 this.refresh();
139 this.refresh();
132 }
140 }
133 return cont;
141 return cont;
134 };
142 };
135
143
136 TextCell.prototype.execute = function () {
144 TextCell.prototype.execute = function () {
137 this.render();
145 this.render();
138 };
146 };
139
147
140 /**
148 /**
141 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
149 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
142 * @method get_text
150 * @method get_text
143 * @retrun {string} CodeMirror current text value
151 * @retrun {string} CodeMirror current text value
144 */
152 */
145 TextCell.prototype.get_text = function() {
153 TextCell.prototype.get_text = function() {
146 return this.code_mirror.getValue();
154 return this.code_mirror.getValue();
147 };
155 };
148
156
149 /**
157 /**
150 * @param {string} text - Codemiror text value
158 * @param {string} text - Codemiror text value
151 * @see TextCell#get_text
159 * @see TextCell#get_text
152 * @method set_text
160 * @method set_text
153 * */
161 * */
154 TextCell.prototype.set_text = function(text) {
162 TextCell.prototype.set_text = function(text) {
155 this.code_mirror.setValue(text);
163 this.code_mirror.setValue(text);
156 this.unrender();
164 this.unrender();
157 this.code_mirror.refresh();
165 this.code_mirror.refresh();
158 };
166 };
159
167
160 /**
168 /**
161 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
169 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
162 * @method get_rendered
170 * @method get_rendered
163 * */
171 * */
164 TextCell.prototype.get_rendered = function() {
172 TextCell.prototype.get_rendered = function() {
165 return this.element.find('div.text_cell_render').html();
173 return this.element.find('div.text_cell_render').html();
166 };
174 };
167
175
168 /**
176 /**
169 * @method set_rendered
177 * @method set_rendered
170 */
178 */
171 TextCell.prototype.set_rendered = function(text) {
179 TextCell.prototype.set_rendered = function(text) {
172 this.element.find('div.text_cell_render').html(text);
180 this.element.find('div.text_cell_render').html(text);
173 };
181 };
174
182
175
183
176 /**
184 /**
177 * Create Text cell from JSON
185 * Create Text cell from JSON
178 * @param {json} data - JSON serialized text-cell
186 * @param {json} data - JSON serialized text-cell
179 * @method fromJSON
187 * @method fromJSON
180 */
188 */
181 TextCell.prototype.fromJSON = function (data) {
189 TextCell.prototype.fromJSON = function (data) {
182 Cell.prototype.fromJSON.apply(this, arguments);
190 Cell.prototype.fromJSON.apply(this, arguments);
183 if (data.cell_type === this.cell_type) {
191 if (data.cell_type === this.cell_type) {
184 if (data.source !== undefined) {
192 if (data.source !== undefined) {
185 this.set_text(data.source);
193 this.set_text(data.source);
186 // make this value the starting point, so that we can only undo
194 // make this value the starting point, so that we can only undo
187 // to this state, instead of a blank cell
195 // to this state, instead of a blank cell
188 this.code_mirror.clearHistory();
196 this.code_mirror.clearHistory();
189 // TODO: This HTML needs to be treated as potentially dangerous
197 // TODO: This HTML needs to be treated as potentially dangerous
190 // user input and should be handled before set_rendered.
198 // user input and should be handled before set_rendered.
191 this.set_rendered(data.rendered || '');
199 this.set_rendered(data.rendered || '');
192 this.rendered = false;
200 this.rendered = false;
193 this.render();
201 this.render();
194 }
202 }
195 }
203 }
196 };
204 };
197
205
198 /** Generate JSON from cell
206 /** Generate JSON from cell
199 * @return {object} cell data serialised to json
207 * @return {object} cell data serialised to json
200 */
208 */
201 TextCell.prototype.toJSON = function () {
209 TextCell.prototype.toJSON = function () {
202 var data = Cell.prototype.toJSON.apply(this);
210 var data = Cell.prototype.toJSON.apply(this);
203 data.source = this.get_text();
211 data.source = this.get_text();
204 if (data.source == this.placeholder) {
212 if (data.source == this.placeholder) {
205 data.source = "";
213 data.source = "";
206 }
214 }
207 return data;
215 return data;
208 };
216 };
209
217
210
218
211 var MarkdownCell = function (options) {
219 var MarkdownCell = function (options) {
212 /**
220 /**
213 * Constructor
221 * Constructor
214 *
222 *
215 * Parameters:
223 * Parameters:
216 * options: dictionary
224 * options: dictionary
217 * Dictionary of keyword arguments.
225 * Dictionary of keyword arguments.
218 * events: $(Events) instance
226 * events: $(Events) instance
219 * config: ConfigSection instance
227 * config: ConfigSection instance
220 * keyboard_manager: KeyboardManager instance
228 * keyboard_manager: KeyboardManager instance
221 * notebook: Notebook instance
229 * notebook: Notebook instance
222 */
230 */
223 options = options || {};
231 options = options || {};
224 var config = utils.mergeopt(MarkdownCell, {});
232 var config = utils.mergeopt(MarkdownCell, {});
225 TextCell.apply(this, [$.extend({}, options, {config: config})]);
233 TextCell.apply(this, [$.extend({}, options, {config: config})]);
226
234
227 this.class_config = new configmod.ConfigWithDefaults(options.config,
235 this.class_config = new configmod.ConfigWithDefaults(options.config,
228 {}, 'MarkdownCell');
236 {}, 'MarkdownCell');
229 this.cell_type = 'markdown';
237 this.cell_type = 'markdown';
230 };
238 };
231
239
232 MarkdownCell.options_default = {
240 MarkdownCell.options_default = {
233 cm_config: {
241 cm_config: {
234 mode: 'ipythongfm'
242 mode: 'ipythongfm'
235 },
243 },
236 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
244 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
237 };
245 };
238
246
239 MarkdownCell.prototype = Object.create(TextCell.prototype);
247 MarkdownCell.prototype = Object.create(TextCell.prototype);
240
248
241 MarkdownCell.prototype.set_heading_level = function (level) {
249 MarkdownCell.prototype.set_heading_level = function (level) {
242 /**
250 /**
243 * make a markdown cell a heading
251 * make a markdown cell a heading
244 */
252 */
245 level = level || 1;
253 level = level || 1;
246 var source = this.get_text();
254 var source = this.get_text();
247 source = source.replace(/^(#*)\s?/,
255 source = source.replace(/^(#*)\s?/,
248 new Array(level + 1).join('#') + ' ');
256 new Array(level + 1).join('#') + ' ');
249 this.set_text(source);
257 this.set_text(source);
250 this.refresh();
258 this.refresh();
251 if (this.rendered) {
259 if (this.rendered) {
252 this.render();
260 this.render();
253 }
261 }
254 };
262 };
255
263
256 /**
264 /**
257 * @method render
265 * @method render
258 */
266 */
259 MarkdownCell.prototype.render = function () {
267 MarkdownCell.prototype.render = function () {
260 var cont = TextCell.prototype.render.apply(this);
268 var cont = TextCell.prototype.render.apply(this);
261 if (cont) {
269 if (cont) {
262 var that = this;
270 var that = this;
263 var text = this.get_text();
271 var text = this.get_text();
264 var math = null;
272 var math = null;
265 if (text === "") { text = this.placeholder; }
273 if (text === "") { text = this.placeholder; }
266 var text_and_math = mathjaxutils.remove_math(text);
274 var text_and_math = mathjaxutils.remove_math(text);
267 text = text_and_math[0];
275 text = text_and_math[0];
268 math = text_and_math[1];
276 math = text_and_math[1];
269 marked(text, function (err, html) {
277 marked(text, function (err, html) {
270 html = mathjaxutils.replace_math(html, math);
278 html = mathjaxutils.replace_math(html, math);
271 html = security.sanitize_html(html);
279 html = security.sanitize_html(html);
272 html = $($.parseHTML(html));
280 html = $($.parseHTML(html));
273 // add anchors to headings
281 // add anchors to headings
274 html.find(":header").addBack(":header").each(function (i, h) {
282 html.find(":header").addBack(":header").each(function (i, h) {
275 h = $(h);
283 h = $(h);
276 var hash = h.text().replace(/ /g, '-');
284 var hash = h.text().replace(/ /g, '-');
277 h.attr('id', hash);
285 h.attr('id', hash);
278 h.append(
286 h.append(
279 $('<a/>')
287 $('<a/>')
280 .addClass('anchor-link')
288 .addClass('anchor-link')
281 .attr('href', '#' + hash)
289 .attr('href', '#' + hash)
282 .text('ΒΆ')
290 .text('ΒΆ')
283 );
291 );
284 });
292 });
285 // links in markdown cells should open in new tabs
293 // links in markdown cells should open in new tabs
286 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
294 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
287 that.set_rendered(html);
295 that.set_rendered(html);
288 that.typeset();
296 that.typeset();
289 that.events.trigger("rendered.MarkdownCell", {cell: that});
297 that.events.trigger("rendered.MarkdownCell", {cell: that});
290 });
298 });
291 }
299 }
292 return cont;
300 return cont;
293 };
301 };
294
302
295
303
296 var RawCell = function (options) {
304 var RawCell = function (options) {
297 /**
305 /**
298 * Constructor
306 * Constructor
299 *
307 *
300 * Parameters:
308 * Parameters:
301 * options: dictionary
309 * options: dictionary
302 * Dictionary of keyword arguments.
310 * Dictionary of keyword arguments.
303 * events: $(Events) instance
311 * events: $(Events) instance
304 * config: ConfigSection instance
312 * config: ConfigSection instance
305 * keyboard_manager: KeyboardManager instance
313 * keyboard_manager: KeyboardManager instance
306 * notebook: Notebook instance
314 * notebook: Notebook instance
307 */
315 */
308 options = options || {};
316 options = options || {};
309 var config = utils.mergeopt(RawCell, {});
317 var config = utils.mergeopt(RawCell, {});
310 TextCell.apply(this, [$.extend({}, options, {config: config})]);
318 TextCell.apply(this, [$.extend({}, options, {config: config})]);
311
319
312 this.class_config = new configmod.ConfigWithDefaults(options.config,
320 this.class_config = new configmod.ConfigWithDefaults(options.config,
313 RawCell.config_defaults, 'RawCell');
321 RawCell.config_defaults, 'RawCell');
314 this.cell_type = 'raw';
322 this.cell_type = 'raw';
315 };
323 };
316
324
317 RawCell.options_default = {
325 RawCell.options_default = {
318 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
326 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
319 "It will not be rendered in the notebook. " +
327 "It will not be rendered in the notebook. " +
320 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
328 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
321 };
329 };
322
330
323 RawCell.config_defaults = {
331 RawCell.config_defaults = {
324 highlight_modes : {
332 highlight_modes : {
325 'diff' :{'reg':[/^diff/]}
333 'diff' :{'reg':[/^diff/]}
326 },
334 },
327 };
335 };
328
336
329 RawCell.prototype = Object.create(TextCell.prototype);
337 RawCell.prototype = Object.create(TextCell.prototype);
330
338
331 /** @method bind_events **/
339 /** @method bind_events **/
332 RawCell.prototype.bind_events = function () {
340 RawCell.prototype.bind_events = function () {
333 TextCell.prototype.bind_events.apply(this);
341 TextCell.prototype.bind_events.apply(this);
334 var that = this;
342 var that = this;
335 this.element.focusout(function() {
343 this.element.focusout(function() {
336 that.auto_highlight();
344 that.auto_highlight();
337 that.render();
345 that.render();
338 });
346 });
339
347
340 this.code_mirror.on('focus', function() { that.unrender(); });
348 this.code_mirror.on('focus', function() { that.unrender(); });
341 };
349 };
342
350
343 /** @method render **/
351 /** @method render **/
344 RawCell.prototype.render = function () {
352 RawCell.prototype.render = function () {
345 var cont = TextCell.prototype.render.apply(this);
353 var cont = TextCell.prototype.render.apply(this);
346 if (cont){
354 if (cont){
347 var text = this.get_text();
355 var text = this.get_text();
348 if (text === "") { text = this.placeholder; }
356 if (text === "") { text = this.placeholder; }
349 this.set_text(text);
357 this.set_text(text);
350 this.element.removeClass('rendered');
358 this.element.removeClass('rendered');
351 this.auto_highlight();
359 this.auto_highlight();
352 }
360 }
353 return cont;
361 return cont;
354 };
362 };
355
363
356 // Backwards compatability.
364 // Backwards compatability.
357 IPython.TextCell = TextCell;
365 IPython.TextCell = TextCell;
358 IPython.MarkdownCell = MarkdownCell;
366 IPython.MarkdownCell = MarkdownCell;
359 IPython.RawCell = RawCell;
367 IPython.RawCell = RawCell;
360
368
361 var textcell = {
369 var textcell = {
362 TextCell: TextCell,
370 TextCell: TextCell,
363 MarkdownCell: MarkdownCell,
371 MarkdownCell: MarkdownCell,
364 RawCell: RawCell
372 RawCell: RawCell
365 };
373 };
366 return textcell;
374 return textcell;
367 });
375 });
General Comments 0
You need to be logged in to leave comments. Login now