##// END OF EJS Templates
Audit .html() calls take #2
Jonathan Frederic -
Show More
@@ -1,570 +1,571 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 CodeCell.msg_cells = {};
108 CodeCell.msg_cells = {};
109
109
110 CodeCell.prototype = new IPython.Cell();
110 CodeCell.prototype = new IPython.Cell();
111
111
112 /**
112 /**
113 * @method auto_highlight
113 * @method auto_highlight
114 */
114 */
115 CodeCell.prototype.auto_highlight = function () {
115 CodeCell.prototype.auto_highlight = function () {
116 this._auto_highlight(IPython.config.cell_magic_highlight);
116 this._auto_highlight(IPython.config.cell_magic_highlight);
117 };
117 };
118
118
119 /** @method create_element */
119 /** @method create_element */
120 CodeCell.prototype.create_element = function () {
120 CodeCell.prototype.create_element = function () {
121 IPython.Cell.prototype.create_element.apply(this, arguments);
121 IPython.Cell.prototype.create_element.apply(this, arguments);
122
122
123 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
123 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
124 cell.attr('tabindex','2');
124 cell.attr('tabindex','2');
125
125
126 var input = $('<div></div>').addClass('input');
126 var input = $('<div></div>').addClass('input');
127 var prompt = $('<div/>').addClass('prompt input_prompt');
127 var prompt = $('<div/>').addClass('prompt input_prompt');
128 var inner_cell = $('<div/>').addClass('inner_cell');
128 var inner_cell = $('<div/>').addClass('inner_cell');
129 this.celltoolbar = new IPython.CellToolbar(this);
129 this.celltoolbar = new IPython.CellToolbar(this);
130 inner_cell.append(this.celltoolbar.element);
130 inner_cell.append(this.celltoolbar.element);
131 var input_area = $('<div/>').addClass('input_area');
131 var input_area = $('<div/>').addClass('input_area');
132 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
132 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
133 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
133 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
134 inner_cell.append(input_area);
134 inner_cell.append(input_area);
135 input.append(prompt).append(inner_cell);
135 input.append(prompt).append(inner_cell);
136
136
137 var widget_area = $('<div/>')
137 var widget_area = $('<div/>')
138 .addClass('widget-area')
138 .addClass('widget-area')
139 .hide();
139 .hide();
140 this.widget_area = widget_area;
140 this.widget_area = widget_area;
141 var widget_prompt = $('<div/>')
141 var widget_prompt = $('<div/>')
142 .addClass('prompt')
142 .addClass('prompt')
143 .appendTo(widget_area);
143 .appendTo(widget_area);
144 var widget_subarea = $('<div/>')
144 var widget_subarea = $('<div/>')
145 .addClass('widget-subarea')
145 .addClass('widget-subarea')
146 .appendTo(widget_area);
146 .appendTo(widget_area);
147 this.widget_subarea = widget_subarea;
147 this.widget_subarea = widget_subarea;
148 var widget_clear_buton = $('<button />')
148 var widget_clear_buton = $('<button />')
149 .addClass('close')
149 .addClass('close')
150 .html('&times;')
150 .html('&times;')
151 .click(function() {
151 .click(function() {
152 widget_area.slideUp('', function(){ widget_subarea.html(''); });
152 widget_area.slideUp('', function(){ widget_subarea.html(''); });
153 })
153 })
154 .appendTo(widget_prompt);
154 .appendTo(widget_prompt);
155
155
156 var output = $('<div></div>');
156 var output = $('<div></div>');
157 cell.append(input).append(widget_area).append(output);
157 cell.append(input).append(widget_area).append(output);
158 this.element = cell;
158 this.element = cell;
159 this.output_area = new IPython.OutputArea(output, true);
159 this.output_area = new IPython.OutputArea(output, true);
160 this.completer = new IPython.Completer(this);
160 this.completer = new IPython.Completer(this);
161 };
161 };
162
162
163 /** @method bind_events */
163 /** @method bind_events */
164 CodeCell.prototype.bind_events = function () {
164 CodeCell.prototype.bind_events = function () {
165 IPython.Cell.prototype.bind_events.apply(this);
165 IPython.Cell.prototype.bind_events.apply(this);
166 var that = this;
166 var that = this;
167
167
168 this.element.focusout(
168 this.element.focusout(
169 function() { that.auto_highlight(); }
169 function() { that.auto_highlight(); }
170 );
170 );
171 };
171 };
172
172
173 CodeCell.prototype.handle_keyevent = function (editor, event) {
173 CodeCell.prototype.handle_keyevent = function (editor, event) {
174
174
175 // console.log('CM', this.mode, event.which, event.type)
175 // console.log('CM', this.mode, event.which, event.type)
176
176
177 if (this.mode === 'command') {
177 if (this.mode === 'command') {
178 return true;
178 return true;
179 } else if (this.mode === 'edit') {
179 } else if (this.mode === 'edit') {
180 return this.handle_codemirror_keyevent(editor, event);
180 return this.handle_codemirror_keyevent(editor, event);
181 }
181 }
182 };
182 };
183
183
184 /**
184 /**
185 * This method gets called in CodeMirror's onKeyDown/onKeyPress
185 * This method gets called in CodeMirror's onKeyDown/onKeyPress
186 * handlers and is used to provide custom key handling. Its return
186 * handlers and is used to provide custom key handling. Its return
187 * value is used to determine if CodeMirror should ignore the event:
187 * value is used to determine if CodeMirror should ignore the event:
188 * true = ignore, false = don't ignore.
188 * true = ignore, false = don't ignore.
189 * @method handle_codemirror_keyevent
189 * @method handle_codemirror_keyevent
190 */
190 */
191 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
191 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
192
192
193 var that = this;
193 var that = this;
194 // whatever key is pressed, first, cancel the tooltip request before
194 // whatever key is pressed, first, cancel the tooltip request before
195 // they are sent, and remove tooltip if any, except for tab again
195 // they are sent, and remove tooltip if any, except for tab again
196 var tooltip_closed = null;
196 var tooltip_closed = null;
197 if (event.type === 'keydown' && event.which != key.TAB ) {
197 if (event.type === 'keydown' && event.which != key.TAB ) {
198 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
198 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
199 }
199 }
200
200
201 var cur = editor.getCursor();
201 var cur = editor.getCursor();
202 if (event.keyCode === key.ENTER){
202 if (event.keyCode === key.ENTER){
203 this.auto_highlight();
203 this.auto_highlight();
204 }
204 }
205
205
206 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey || event.altKey)) {
206 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey || event.altKey)) {
207 // Always ignore shift-enter in CodeMirror as we handle it.
207 // Always ignore shift-enter in CodeMirror as we handle it.
208 return true;
208 return true;
209 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
209 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
210 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
210 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
211 // browser and keyboard layout !
211 // browser and keyboard layout !
212 // Pressing '(' , request tooltip, don't forget to reappend it
212 // Pressing '(' , request tooltip, don't forget to reappend it
213 // The second argument says to hide the tooltip if the docstring
213 // The second argument says to hide the tooltip if the docstring
214 // is actually empty
214 // is actually empty
215 IPython.tooltip.pending(that, true);
215 IPython.tooltip.pending(that, true);
216 } else if (event.which === key.UPARROW && event.type === 'keydown') {
216 } else if (event.which === key.UPARROW && event.type === 'keydown') {
217 // If we are not at the top, let CM handle the up arrow and
217 // If we are not at the top, let CM handle the up arrow and
218 // prevent the global keydown handler from handling it.
218 // prevent the global keydown handler from handling it.
219 if (!that.at_top()) {
219 if (!that.at_top()) {
220 event.stop();
220 event.stop();
221 return false;
221 return false;
222 } else {
222 } else {
223 return true;
223 return true;
224 }
224 }
225 } else if (event.which === key.ESC && event.type === 'keydown') {
225 } else if (event.which === key.ESC && event.type === 'keydown') {
226 // First see if the tooltip is active and if so cancel it.
226 // First see if the tooltip is active and if so cancel it.
227 if (tooltip_closed) {
227 if (tooltip_closed) {
228 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
228 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
229 // force=true. Because of this it won't actually close the tooltip
229 // force=true. Because of this it won't actually close the tooltip
230 // if it is in sticky mode. Thus, we have to check again if it is open
230 // if it is in sticky mode. Thus, we have to check again if it is open
231 // and close it with force=true.
231 // and close it with force=true.
232 if (!IPython.tooltip._hidden) {
232 if (!IPython.tooltip._hidden) {
233 IPython.tooltip.remove_and_cancel_tooltip(true);
233 IPython.tooltip.remove_and_cancel_tooltip(true);
234 }
234 }
235 // If we closed the tooltip, don't let CM or the global handlers
235 // If we closed the tooltip, don't let CM or the global handlers
236 // handle this event.
236 // handle this event.
237 event.stop();
237 event.stop();
238 return true;
238 return true;
239 }
239 }
240 if (that.code_mirror.options.keyMap === "vim-insert") {
240 if (that.code_mirror.options.keyMap === "vim-insert") {
241 // vim keyMap is active and in insert mode. In this case we leave vim
241 // vim keyMap is active and in insert mode. In this case we leave vim
242 // insert mode, but remain in notebook edit mode.
242 // insert mode, but remain in notebook edit mode.
243 // Let' CM handle this event and prevent global handling.
243 // Let' CM handle this event and prevent global handling.
244 event.stop();
244 event.stop();
245 return false;
245 return false;
246 } else {
246 } else {
247 // vim keyMap is not active. Leave notebook edit mode.
247 // vim keyMap is not active. Leave notebook edit mode.
248 // Don't let CM handle the event, defer to global handling.
248 // Don't let CM handle the event, defer to global handling.
249 return true;
249 return true;
250 }
250 }
251 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
251 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
252 // If we are not at the bottom, let CM handle the down arrow and
252 // If we are not at the bottom, let CM handle the down arrow and
253 // prevent the global keydown handler from handling it.
253 // prevent the global keydown handler from handling it.
254 if (!that.at_bottom()) {
254 if (!that.at_bottom()) {
255 event.stop();
255 event.stop();
256 return false;
256 return false;
257 } else {
257 } else {
258 return true;
258 return true;
259 }
259 }
260 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
260 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
261 if (editor.somethingSelected()){
261 if (editor.somethingSelected()){
262 var anchor = editor.getCursor("anchor");
262 var anchor = editor.getCursor("anchor");
263 var head = editor.getCursor("head");
263 var head = editor.getCursor("head");
264 if( anchor.line != head.line){
264 if( anchor.line != head.line){
265 return false;
265 return false;
266 }
266 }
267 }
267 }
268 IPython.tooltip.request(that);
268 IPython.tooltip.request(that);
269 event.stop();
269 event.stop();
270 return true;
270 return true;
271 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
271 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
272 // Tab completion.
272 // Tab completion.
273 IPython.tooltip.remove_and_cancel_tooltip();
273 IPython.tooltip.remove_and_cancel_tooltip();
274 if (editor.somethingSelected()) {
274 if (editor.somethingSelected()) {
275 return false;
275 return false;
276 }
276 }
277 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
277 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
278 if (pre_cursor.trim() === "") {
278 if (pre_cursor.trim() === "") {
279 // Don't autocomplete if the part of the line before the cursor
279 // Don't autocomplete if the part of the line before the cursor
280 // is empty. In this case, let CodeMirror handle indentation.
280 // is empty. In this case, let CodeMirror handle indentation.
281 return false;
281 return false;
282 } else {
282 } else {
283 event.stop();
283 event.stop();
284 this.completer.startCompletion();
284 this.completer.startCompletion();
285 return true;
285 return true;
286 }
286 }
287 } else {
287 } else {
288 // keypress/keyup also trigger on TAB press, and we don't want to
288 // keypress/keyup also trigger on TAB press, and we don't want to
289 // use those to disable tab completion.
289 // use those to disable tab completion.
290 return false;
290 return false;
291 }
291 }
292 return false;
292 return false;
293 };
293 };
294
294
295 // Kernel related calls.
295 // Kernel related calls.
296
296
297 CodeCell.prototype.set_kernel = function (kernel) {
297 CodeCell.prototype.set_kernel = function (kernel) {
298 this.kernel = kernel;
298 this.kernel = kernel;
299 };
299 };
300
300
301 /**
301 /**
302 * Execute current code cell to the kernel
302 * Execute current code cell to the kernel
303 * @method execute
303 * @method execute
304 */
304 */
305 CodeCell.prototype.execute = function () {
305 CodeCell.prototype.execute = function () {
306 this.output_area.clear_output();
306 this.output_area.clear_output();
307
307
308 // Clear widget area
308 // Clear widget area
309 this.widget_subarea.html('');
309 this.widget_subarea.html('');
310 this.widget_subarea.height('');
310 this.widget_subarea.height('');
311 this.widget_area.height('');
311 this.widget_area.height('');
312 this.widget_area.hide();
312 this.widget_area.hide();
313
313
314 this.set_input_prompt('*');
314 this.set_input_prompt('*');
315 this.element.addClass("running");
315 this.element.addClass("running");
316 if (this.last_msg_id) {
316 if (this.last_msg_id) {
317 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
317 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
318 }
318 }
319 var callbacks = this.get_callbacks();
319 var callbacks = this.get_callbacks();
320
320
321 var old_msg_id = this.last_msg_id;
321 var old_msg_id = this.last_msg_id;
322 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
322 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
323 if (old_msg_id) {
323 if (old_msg_id) {
324 delete CodeCell.msg_cells[old_msg_id];
324 delete CodeCell.msg_cells[old_msg_id];
325 }
325 }
326 CodeCell.msg_cells[this.last_msg_id] = this;
326 CodeCell.msg_cells[this.last_msg_id] = this;
327 };
327 };
328
328
329 /**
329 /**
330 * Construct the default callbacks for
330 * Construct the default callbacks for
331 * @method get_callbacks
331 * @method get_callbacks
332 */
332 */
333 CodeCell.prototype.get_callbacks = function () {
333 CodeCell.prototype.get_callbacks = function () {
334 return {
334 return {
335 shell : {
335 shell : {
336 reply : $.proxy(this._handle_execute_reply, this),
336 reply : $.proxy(this._handle_execute_reply, this),
337 payload : {
337 payload : {
338 set_next_input : $.proxy(this._handle_set_next_input, this),
338 set_next_input : $.proxy(this._handle_set_next_input, this),
339 page : $.proxy(this._open_with_pager, this)
339 page : $.proxy(this._open_with_pager, this)
340 }
340 }
341 },
341 },
342 iopub : {
342 iopub : {
343 output : $.proxy(this.output_area.handle_output, this.output_area),
343 output : $.proxy(this.output_area.handle_output, this.output_area),
344 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
344 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
345 },
345 },
346 input : $.proxy(this._handle_input_request, this)
346 input : $.proxy(this._handle_input_request, this)
347 };
347 };
348 };
348 };
349
349
350 CodeCell.prototype._open_with_pager = function (payload) {
350 CodeCell.prototype._open_with_pager = function (payload) {
351 $([IPython.events]).trigger('open_with_text.Pager', payload);
351 $([IPython.events]).trigger('open_with_text.Pager', payload);
352 };
352 };
353
353
354 /**
354 /**
355 * @method _handle_execute_reply
355 * @method _handle_execute_reply
356 * @private
356 * @private
357 */
357 */
358 CodeCell.prototype._handle_execute_reply = function (msg) {
358 CodeCell.prototype._handle_execute_reply = function (msg) {
359 this.set_input_prompt(msg.content.execution_count);
359 this.set_input_prompt(msg.content.execution_count);
360 this.element.removeClass("running");
360 this.element.removeClass("running");
361 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
361 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
362 };
362 };
363
363
364 /**
364 /**
365 * @method _handle_set_next_input
365 * @method _handle_set_next_input
366 * @private
366 * @private
367 */
367 */
368 CodeCell.prototype._handle_set_next_input = function (payload) {
368 CodeCell.prototype._handle_set_next_input = function (payload) {
369 var data = {'cell': this, 'text': payload.text};
369 var data = {'cell': this, 'text': payload.text};
370 $([IPython.events]).trigger('set_next_input.Notebook', data);
370 $([IPython.events]).trigger('set_next_input.Notebook', data);
371 };
371 };
372
372
373 /**
373 /**
374 * @method _handle_input_request
374 * @method _handle_input_request
375 * @private
375 * @private
376 */
376 */
377 CodeCell.prototype._handle_input_request = function (msg) {
377 CodeCell.prototype._handle_input_request = function (msg) {
378 this.output_area.append_raw_input(msg);
378 this.output_area.append_raw_input(msg);
379 };
379 };
380
380
381
381
382 // Basic cell manipulation.
382 // Basic cell manipulation.
383
383
384 CodeCell.prototype.select = function () {
384 CodeCell.prototype.select = function () {
385 var cont = IPython.Cell.prototype.select.apply(this);
385 var cont = IPython.Cell.prototype.select.apply(this);
386 if (cont) {
386 if (cont) {
387 this.code_mirror.refresh();
387 this.code_mirror.refresh();
388 this.auto_highlight();
388 this.auto_highlight();
389 }
389 }
390 return cont;
390 return cont;
391 };
391 };
392
392
393 CodeCell.prototype.render = function () {
393 CodeCell.prototype.render = function () {
394 var cont = IPython.Cell.prototype.render.apply(this);
394 var cont = IPython.Cell.prototype.render.apply(this);
395 // Always execute, even if we are already in the rendered state
395 // Always execute, even if we are already in the rendered state
396 return cont;
396 return cont;
397 };
397 };
398
398
399 CodeCell.prototype.unrender = function () {
399 CodeCell.prototype.unrender = function () {
400 // CodeCell is always rendered
400 // CodeCell is always rendered
401 return false;
401 return false;
402 };
402 };
403
403
404 CodeCell.prototype.edit_mode = function () {
404 CodeCell.prototype.edit_mode = function () {
405 var cont = IPython.Cell.prototype.edit_mode.apply(this);
405 var cont = IPython.Cell.prototype.edit_mode.apply(this);
406 if (cont) {
406 if (cont) {
407 this.focus_editor();
407 this.focus_editor();
408 }
408 }
409 return cont;
409 return cont;
410 }
410 }
411
411
412 CodeCell.prototype.select_all = function () {
412 CodeCell.prototype.select_all = function () {
413 var start = {line: 0, ch: 0};
413 var start = {line: 0, ch: 0};
414 var nlines = this.code_mirror.lineCount();
414 var nlines = this.code_mirror.lineCount();
415 var last_line = this.code_mirror.getLine(nlines-1);
415 var last_line = this.code_mirror.getLine(nlines-1);
416 var end = {line: nlines-1, ch: last_line.length};
416 var end = {line: nlines-1, ch: last_line.length};
417 this.code_mirror.setSelection(start, end);
417 this.code_mirror.setSelection(start, end);
418 };
418 };
419
419
420
420
421 CodeCell.prototype.collapse_output = function () {
421 CodeCell.prototype.collapse_output = function () {
422 this.collapsed = true;
422 this.collapsed = true;
423 this.output_area.collapse();
423 this.output_area.collapse();
424 };
424 };
425
425
426
426
427 CodeCell.prototype.expand_output = function () {
427 CodeCell.prototype.expand_output = function () {
428 this.collapsed = false;
428 this.collapsed = false;
429 this.output_area.expand();
429 this.output_area.expand();
430 this.output_area.unscroll_area();
430 this.output_area.unscroll_area();
431 };
431 };
432
432
433 CodeCell.prototype.scroll_output = function () {
433 CodeCell.prototype.scroll_output = function () {
434 this.output_area.expand();
434 this.output_area.expand();
435 this.output_area.scroll_if_long();
435 this.output_area.scroll_if_long();
436 };
436 };
437
437
438 CodeCell.prototype.toggle_output = function () {
438 CodeCell.prototype.toggle_output = function () {
439 this.collapsed = Boolean(1 - this.collapsed);
439 this.collapsed = Boolean(1 - this.collapsed);
440 this.output_area.toggle_output();
440 this.output_area.toggle_output();
441 };
441 };
442
442
443 CodeCell.prototype.toggle_output_scroll = function () {
443 CodeCell.prototype.toggle_output_scroll = function () {
444 this.output_area.toggle_scroll();
444 this.output_area.toggle_scroll();
445 };
445 };
446
446
447
447
448 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
448 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
449 var ns;
449 var ns;
450 if (prompt_value == undefined) {
450 if (prompt_value == undefined) {
451 ns = "&nbsp;";
451 ns = "&nbsp;";
452 } else {
452 } else {
453 ns = encodeURIComponent(prompt_value);
453 ns = encodeURIComponent(prompt_value);
454 }
454 }
455 return 'In&nbsp;[' + ns + ']:';
455 return 'In&nbsp;[' + ns + ']:';
456 };
456 };
457
457
458 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
458 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
459 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
459 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
460 for(var i=1; i < lines_number; i++) {
460 for(var i=1; i < lines_number; i++) {
461 html.push(['...:']);
461 html.push(['...:']);
462 }
462 }
463 return html.join('<br/>');
463 return html.join('<br/>');
464 };
464 };
465
465
466 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
466 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
467
467
468
468
469 CodeCell.prototype.set_input_prompt = function (number) {
469 CodeCell.prototype.set_input_prompt = function (number) {
470 var nline = 1;
470 var nline = 1;
471 if (this.code_mirror !== undefined) {
471 if (this.code_mirror !== undefined) {
472 nline = this.code_mirror.lineCount();
472 nline = this.code_mirror.lineCount();
473 }
473 }
474 this.input_prompt_number = number;
474 this.input_prompt_number = number;
475 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
475 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
476 // This HTML call is okay because the user contents are escaped.
476 this.element.find('div.input_prompt').html(prompt_html);
477 this.element.find('div.input_prompt').html(prompt_html);
477 };
478 };
478
479
479
480
480 CodeCell.prototype.clear_input = function () {
481 CodeCell.prototype.clear_input = function () {
481 this.code_mirror.setValue('');
482 this.code_mirror.setValue('');
482 };
483 };
483
484
484
485
485 CodeCell.prototype.get_text = function () {
486 CodeCell.prototype.get_text = function () {
486 return this.code_mirror.getValue();
487 return this.code_mirror.getValue();
487 };
488 };
488
489
489
490
490 CodeCell.prototype.set_text = function (code) {
491 CodeCell.prototype.set_text = function (code) {
491 return this.code_mirror.setValue(code);
492 return this.code_mirror.setValue(code);
492 };
493 };
493
494
494
495
495 CodeCell.prototype.at_top = function () {
496 CodeCell.prototype.at_top = function () {
496 var cursor = this.code_mirror.getCursor();
497 var cursor = this.code_mirror.getCursor();
497 if (cursor.line === 0 && cursor.ch === 0) {
498 if (cursor.line === 0 && cursor.ch === 0) {
498 return true;
499 return true;
499 } else {
500 } else {
500 return false;
501 return false;
501 }
502 }
502 };
503 };
503
504
504
505
505 CodeCell.prototype.at_bottom = function () {
506 CodeCell.prototype.at_bottom = function () {
506 var cursor = this.code_mirror.getCursor();
507 var cursor = this.code_mirror.getCursor();
507 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
508 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
508 return true;
509 return true;
509 } else {
510 } else {
510 return false;
511 return false;
511 }
512 }
512 };
513 };
513
514
514
515
515 CodeCell.prototype.clear_output = function (wait) {
516 CodeCell.prototype.clear_output = function (wait) {
516 this.output_area.clear_output(wait);
517 this.output_area.clear_output(wait);
517 this.set_input_prompt();
518 this.set_input_prompt();
518 };
519 };
519
520
520
521
521 // JSON serialization
522 // JSON serialization
522
523
523 CodeCell.prototype.fromJSON = function (data) {
524 CodeCell.prototype.fromJSON = function (data) {
524 IPython.Cell.prototype.fromJSON.apply(this, arguments);
525 IPython.Cell.prototype.fromJSON.apply(this, arguments);
525 if (data.cell_type === 'code') {
526 if (data.cell_type === 'code') {
526 if (data.input !== undefined) {
527 if (data.input !== undefined) {
527 this.set_text(data.input);
528 this.set_text(data.input);
528 // make this value the starting point, so that we can only undo
529 // make this value the starting point, so that we can only undo
529 // to this state, instead of a blank cell
530 // to this state, instead of a blank cell
530 this.code_mirror.clearHistory();
531 this.code_mirror.clearHistory();
531 this.auto_highlight();
532 this.auto_highlight();
532 }
533 }
533 if (data.prompt_number !== undefined) {
534 if (data.prompt_number !== undefined) {
534 this.set_input_prompt(data.prompt_number);
535 this.set_input_prompt(data.prompt_number);
535 } else {
536 } else {
536 this.set_input_prompt();
537 this.set_input_prompt();
537 }
538 }
538 this.output_area.trusted = data.trusted || false;
539 this.output_area.trusted = data.trusted || false;
539 this.output_area.fromJSON(data.outputs);
540 this.output_area.fromJSON(data.outputs);
540 if (data.collapsed !== undefined) {
541 if (data.collapsed !== undefined) {
541 if (data.collapsed) {
542 if (data.collapsed) {
542 this.collapse_output();
543 this.collapse_output();
543 } else {
544 } else {
544 this.expand_output();
545 this.expand_output();
545 }
546 }
546 }
547 }
547 }
548 }
548 };
549 };
549
550
550
551
551 CodeCell.prototype.toJSON = function () {
552 CodeCell.prototype.toJSON = function () {
552 var data = IPython.Cell.prototype.toJSON.apply(this);
553 var data = IPython.Cell.prototype.toJSON.apply(this);
553 data.input = this.get_text();
554 data.input = this.get_text();
554 // is finite protect against undefined and '*' value
555 // is finite protect against undefined and '*' value
555 if (isFinite(this.input_prompt_number)) {
556 if (isFinite(this.input_prompt_number)) {
556 data.prompt_number = this.input_prompt_number;
557 data.prompt_number = this.input_prompt_number;
557 }
558 }
558 var outputs = this.output_area.toJSON();
559 var outputs = this.output_area.toJSON();
559 data.outputs = outputs;
560 data.outputs = outputs;
560 data.language = 'python';
561 data.language = 'python';
561 data.trusted = this.output_area.trusted;
562 data.trusted = this.output_area.trusted;
562 data.collapsed = this.collapsed;
563 data.collapsed = this.collapsed;
563 return data;
564 return data;
564 };
565 };
565
566
566
567
567 IPython.CodeCell = CodeCell;
568 IPython.CodeCell = CodeCell;
568
569
569 return IPython;
570 return IPython;
570 }(IPython));
571 }(IPython));
@@ -1,866 +1,869 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 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 // OutputArea
9 // OutputArea
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule OutputArea
15 * @submodule OutputArea
16 */
16 */
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 /**
22 /**
23 * @class OutputArea
23 * @class OutputArea
24 *
24 *
25 * @constructor
25 * @constructor
26 */
26 */
27
27
28 var OutputArea = function (selector, prompt_area) {
28 var OutputArea = function (selector, prompt_area) {
29 this.selector = selector;
29 this.selector = selector;
30 this.wrapper = $(selector);
30 this.wrapper = $(selector);
31 this.outputs = [];
31 this.outputs = [];
32 this.collapsed = false;
32 this.collapsed = false;
33 this.scrolled = false;
33 this.scrolled = false;
34 this.trusted = true;
34 this.trusted = true;
35 this.clear_queued = null;
35 this.clear_queued = null;
36 if (prompt_area === undefined) {
36 if (prompt_area === undefined) {
37 this.prompt_area = true;
37 this.prompt_area = true;
38 } else {
38 } else {
39 this.prompt_area = prompt_area;
39 this.prompt_area = prompt_area;
40 }
40 }
41 this.create_elements();
41 this.create_elements();
42 this.style();
42 this.style();
43 this.bind_events();
43 this.bind_events();
44 };
44 };
45
45
46
46
47 /**
47 /**
48 * Class prototypes
48 * Class prototypes
49 **/
49 **/
50
50
51 OutputArea.prototype.create_elements = function () {
51 OutputArea.prototype.create_elements = function () {
52 this.element = $("<div/>");
52 this.element = $("<div/>");
53 this.collapse_button = $("<div/>");
53 this.collapse_button = $("<div/>");
54 this.prompt_overlay = $("<div/>");
54 this.prompt_overlay = $("<div/>");
55 this.wrapper.append(this.prompt_overlay);
55 this.wrapper.append(this.prompt_overlay);
56 this.wrapper.append(this.element);
56 this.wrapper.append(this.element);
57 this.wrapper.append(this.collapse_button);
57 this.wrapper.append(this.collapse_button);
58 };
58 };
59
59
60
60
61 OutputArea.prototype.style = function () {
61 OutputArea.prototype.style = function () {
62 this.collapse_button.hide();
62 this.collapse_button.hide();
63 this.prompt_overlay.hide();
63 this.prompt_overlay.hide();
64
64
65 this.wrapper.addClass('output_wrapper');
65 this.wrapper.addClass('output_wrapper');
66 this.element.addClass('output');
66 this.element.addClass('output');
67
67
68 this.collapse_button.addClass("btn output_collapsed");
68 this.collapse_button.addClass("btn output_collapsed");
69 this.collapse_button.attr('title', 'click to expand output');
69 this.collapse_button.attr('title', 'click to expand output');
70 this.collapse_button.text('. . .');
70 this.collapse_button.text('. . .');
71
71
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
74
74
75 this.collapse();
75 this.collapse();
76 };
76 };
77
77
78 /**
78 /**
79 * Should the OutputArea scroll?
79 * Should the OutputArea scroll?
80 * Returns whether the height (in lines) exceeds a threshold.
80 * Returns whether the height (in lines) exceeds a threshold.
81 *
81 *
82 * @private
82 * @private
83 * @method _should_scroll
83 * @method _should_scroll
84 * @param [lines=100]{Integer}
84 * @param [lines=100]{Integer}
85 * @return {Bool}
85 * @return {Bool}
86 *
86 *
87 */
87 */
88 OutputArea.prototype._should_scroll = function (lines) {
88 OutputArea.prototype._should_scroll = function (lines) {
89 if (lines <=0 ){ return }
89 if (lines <=0 ){ return }
90 if (!lines) {
90 if (!lines) {
91 lines = 100;
91 lines = 100;
92 }
92 }
93 // line-height from http://stackoverflow.com/questions/1185151
93 // line-height from http://stackoverflow.com/questions/1185151
94 var fontSize = this.element.css('font-size');
94 var fontSize = this.element.css('font-size');
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
96
96
97 return (this.element.height() > lines * lineHeight);
97 return (this.element.height() > lines * lineHeight);
98 };
98 };
99
99
100
100
101 OutputArea.prototype.bind_events = function () {
101 OutputArea.prototype.bind_events = function () {
102 var that = this;
102 var that = this;
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
105
105
106 this.element.resize(function () {
106 this.element.resize(function () {
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
108 if ( IPython.utils.browser[0] === "Firefox" ) {
108 if ( IPython.utils.browser[0] === "Firefox" ) {
109 return;
109 return;
110 }
110 }
111 // maybe scroll output,
111 // maybe scroll output,
112 // if it's grown large enough and hasn't already been scrolled.
112 // if it's grown large enough and hasn't already been scrolled.
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
114 that.scroll_area();
114 that.scroll_area();
115 }
115 }
116 });
116 });
117 this.collapse_button.click(function () {
117 this.collapse_button.click(function () {
118 that.expand();
118 that.expand();
119 });
119 });
120 };
120 };
121
121
122
122
123 OutputArea.prototype.collapse = function () {
123 OutputArea.prototype.collapse = function () {
124 if (!this.collapsed) {
124 if (!this.collapsed) {
125 this.element.hide();
125 this.element.hide();
126 this.prompt_overlay.hide();
126 this.prompt_overlay.hide();
127 if (this.element.html()){
127 if (this.element.html()){
128 this.collapse_button.show();
128 this.collapse_button.show();
129 }
129 }
130 this.collapsed = true;
130 this.collapsed = true;
131 }
131 }
132 };
132 };
133
133
134
134
135 OutputArea.prototype.expand = function () {
135 OutputArea.prototype.expand = function () {
136 if (this.collapsed) {
136 if (this.collapsed) {
137 this.collapse_button.hide();
137 this.collapse_button.hide();
138 this.element.show();
138 this.element.show();
139 this.prompt_overlay.show();
139 this.prompt_overlay.show();
140 this.collapsed = false;
140 this.collapsed = false;
141 }
141 }
142 };
142 };
143
143
144
144
145 OutputArea.prototype.toggle_output = function () {
145 OutputArea.prototype.toggle_output = function () {
146 if (this.collapsed) {
146 if (this.collapsed) {
147 this.expand();
147 this.expand();
148 } else {
148 } else {
149 this.collapse();
149 this.collapse();
150 }
150 }
151 };
151 };
152
152
153
153
154 OutputArea.prototype.scroll_area = function () {
154 OutputArea.prototype.scroll_area = function () {
155 this.element.addClass('output_scroll');
155 this.element.addClass('output_scroll');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
157 this.scrolled = true;
157 this.scrolled = true;
158 };
158 };
159
159
160
160
161 OutputArea.prototype.unscroll_area = function () {
161 OutputArea.prototype.unscroll_area = function () {
162 this.element.removeClass('output_scroll');
162 this.element.removeClass('output_scroll');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
164 this.scrolled = false;
164 this.scrolled = false;
165 };
165 };
166
166
167 /**
167 /**
168 *
168 *
169 * Scroll OutputArea if height supperior than a threshold (in lines).
169 * Scroll OutputArea if height supperior than a threshold (in lines).
170 *
170 *
171 * Threshold is a maximum number of lines. If unspecified, defaults to
171 * Threshold is a maximum number of lines. If unspecified, defaults to
172 * OutputArea.minimum_scroll_threshold.
172 * OutputArea.minimum_scroll_threshold.
173 *
173 *
174 * Negative threshold will prevent the OutputArea from ever scrolling.
174 * Negative threshold will prevent the OutputArea from ever scrolling.
175 *
175 *
176 * @method scroll_if_long
176 * @method scroll_if_long
177 *
177 *
178 * @param [lines=20]{Number} Default to 20 if not set,
178 * @param [lines=20]{Number} Default to 20 if not set,
179 * behavior undefined for value of `0`.
179 * behavior undefined for value of `0`.
180 *
180 *
181 **/
181 **/
182 OutputArea.prototype.scroll_if_long = function (lines) {
182 OutputArea.prototype.scroll_if_long = function (lines) {
183 var n = lines | OutputArea.minimum_scroll_threshold;
183 var n = lines | OutputArea.minimum_scroll_threshold;
184 if(n <= 0){
184 if(n <= 0){
185 return
185 return
186 }
186 }
187
187
188 if (this._should_scroll(n)) {
188 if (this._should_scroll(n)) {
189 // only allow scrolling long-enough output
189 // only allow scrolling long-enough output
190 this.scroll_area();
190 this.scroll_area();
191 }
191 }
192 };
192 };
193
193
194
194
195 OutputArea.prototype.toggle_scroll = function () {
195 OutputArea.prototype.toggle_scroll = function () {
196 if (this.scrolled) {
196 if (this.scrolled) {
197 this.unscroll_area();
197 this.unscroll_area();
198 } else {
198 } else {
199 // only allow scrolling long-enough output
199 // only allow scrolling long-enough output
200 this.scroll_if_long();
200 this.scroll_if_long();
201 }
201 }
202 };
202 };
203
203
204
204
205 // typeset with MathJax if MathJax is available
205 // typeset with MathJax if MathJax is available
206 OutputArea.prototype.typeset = function () {
206 OutputArea.prototype.typeset = function () {
207 if (window.MathJax){
207 if (window.MathJax){
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
209 }
209 }
210 };
210 };
211
211
212
212
213 OutputArea.prototype.handle_output = function (msg) {
213 OutputArea.prototype.handle_output = function (msg) {
214 var json = {};
214 var json = {};
215 var msg_type = json.output_type = msg.header.msg_type;
215 var msg_type = json.output_type = msg.header.msg_type;
216 var content = msg.content;
216 var content = msg.content;
217 if (msg_type === "stream") {
217 if (msg_type === "stream") {
218 json.text = content.data;
218 json.text = content.data;
219 json.stream = content.name;
219 json.stream = content.name;
220 } else if (msg_type === "display_data") {
220 } else if (msg_type === "display_data") {
221 json = content.data;
221 json = content.data;
222 json.output_type = msg_type;
222 json.output_type = msg_type;
223 json.metadata = content.metadata;
223 json.metadata = content.metadata;
224 } else if (msg_type === "pyout") {
224 } else if (msg_type === "pyout") {
225 json = content.data;
225 json = content.data;
226 json.output_type = msg_type;
226 json.output_type = msg_type;
227 json.metadata = content.metadata;
227 json.metadata = content.metadata;
228 json.prompt_number = content.execution_count;
228 json.prompt_number = content.execution_count;
229 } else if (msg_type === "pyerr") {
229 } else if (msg_type === "pyerr") {
230 json.ename = content.ename;
230 json.ename = content.ename;
231 json.evalue = content.evalue;
231 json.evalue = content.evalue;
232 json.traceback = content.traceback;
232 json.traceback = content.traceback;
233 }
233 }
234 this.append_output(json);
234 this.append_output(json);
235 };
235 };
236
236
237
237
238 OutputArea.prototype.rename_keys = function (data, key_map) {
238 OutputArea.prototype.rename_keys = function (data, key_map) {
239 var remapped = {};
239 var remapped = {};
240 for (var key in data) {
240 for (var key in data) {
241 var new_key = key_map[key] || key;
241 var new_key = key_map[key] || key;
242 remapped[new_key] = data[key];
242 remapped[new_key] = data[key];
243 }
243 }
244 return remapped;
244 return remapped;
245 };
245 };
246
246
247
247
248 OutputArea.output_types = [
248 OutputArea.output_types = [
249 'application/javascript',
249 'application/javascript',
250 'text/html',
250 'text/html',
251 'text/latex',
251 'text/latex',
252 'image/svg+xml',
252 'image/svg+xml',
253 'image/png',
253 'image/png',
254 'image/jpeg',
254 'image/jpeg',
255 'application/pdf',
255 'application/pdf',
256 'text/plain'
256 'text/plain'
257 ];
257 ];
258
258
259 OutputArea.prototype.validate_output = function (json) {
259 OutputArea.prototype.validate_output = function (json) {
260 // scrub invalid outputs
260 // scrub invalid outputs
261 // TODO: right now everything is a string, but JSON really shouldn't be.
261 // TODO: right now everything is a string, but JSON really shouldn't be.
262 // nbformat 4 will fix that.
262 // nbformat 4 will fix that.
263 $.map(OutputArea.output_types, function(key){
263 $.map(OutputArea.output_types, function(key){
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
265 console.log("Invalid type for " + key, json[key]);
265 console.log("Invalid type for " + key, json[key]);
266 delete json[key];
266 delete json[key];
267 }
267 }
268 });
268 });
269 return json;
269 return json;
270 };
270 };
271
271
272 OutputArea.prototype.append_output = function (json) {
272 OutputArea.prototype.append_output = function (json) {
273 this.expand();
273 this.expand();
274 // Clear the output if clear is queued.
274 // Clear the output if clear is queued.
275 var needs_height_reset = false;
275 var needs_height_reset = false;
276 if (this.clear_queued) {
276 if (this.clear_queued) {
277 this.clear_output(false);
277 this.clear_output(false);
278 needs_height_reset = true;
278 needs_height_reset = true;
279 }
279 }
280
280
281 // validate output data types
281 // validate output data types
282 json = this.validate_output(json);
282 json = this.validate_output(json);
283
283
284 if (json.output_type === 'pyout') {
284 if (json.output_type === 'pyout') {
285 this.append_pyout(json);
285 this.append_pyout(json);
286 } else if (json.output_type === 'pyerr') {
286 } else if (json.output_type === 'pyerr') {
287 this.append_pyerr(json);
287 this.append_pyerr(json);
288 } else if (json.output_type === 'display_data') {
288 } else if (json.output_type === 'display_data') {
289 this.append_display_data(json);
289 this.append_display_data(json);
290 } else if (json.output_type === 'stream') {
290 } else if (json.output_type === 'stream') {
291 this.append_stream(json);
291 this.append_stream(json);
292 }
292 }
293
293
294 this.outputs.push(json);
294 this.outputs.push(json);
295
295
296 // Only reset the height to automatic if the height is currently
296 // Only reset the height to automatic if the height is currently
297 // fixed (done by wait=True flag on clear_output).
297 // fixed (done by wait=True flag on clear_output).
298 if (needs_height_reset) {
298 if (needs_height_reset) {
299 this.element.height('');
299 this.element.height('');
300 }
300 }
301
301
302 var that = this;
302 var that = this;
303 setTimeout(function(){that.element.trigger('resize');}, 100);
303 setTimeout(function(){that.element.trigger('resize');}, 100);
304 };
304 };
305
305
306
306
307 OutputArea.prototype.create_output_area = function () {
307 OutputArea.prototype.create_output_area = function () {
308 var oa = $("<div/>").addClass("output_area");
308 var oa = $("<div/>").addClass("output_area");
309 if (this.prompt_area) {
309 if (this.prompt_area) {
310 oa.append($('<div/>').addClass('prompt'));
310 oa.append($('<div/>').addClass('prompt'));
311 }
311 }
312 return oa;
312 return oa;
313 };
313 };
314
314
315
315
316 function _get_metadata_key(metadata, key, mime) {
316 function _get_metadata_key(metadata, key, mime) {
317 var mime_md = metadata[mime];
317 var mime_md = metadata[mime];
318 // mime-specific higher priority
318 // mime-specific higher priority
319 if (mime_md && mime_md[key] !== undefined) {
319 if (mime_md && mime_md[key] !== undefined) {
320 return mime_md[key];
320 return mime_md[key];
321 }
321 }
322 // fallback on global
322 // fallback on global
323 return metadata[key];
323 return metadata[key];
324 }
324 }
325
325
326 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
326 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
327 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
327 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
328 if (_get_metadata_key(md, 'isolated', mime)) {
328 if (_get_metadata_key(md, 'isolated', mime)) {
329 // Create an iframe to isolate the subarea from the rest of the
329 // Create an iframe to isolate the subarea from the rest of the
330 // document
330 // document
331 var iframe = $('<iframe/>').addClass('box-flex1');
331 var iframe = $('<iframe/>').addClass('box-flex1');
332 iframe.css({'height':1, 'width':'100%', 'display':'block'});
332 iframe.css({'height':1, 'width':'100%', 'display':'block'});
333 iframe.attr('frameborder', 0);
333 iframe.attr('frameborder', 0);
334 iframe.attr('scrolling', 'auto');
334 iframe.attr('scrolling', 'auto');
335
335
336 // Once the iframe is loaded, the subarea is dynamically inserted
336 // Once the iframe is loaded, the subarea is dynamically inserted
337 iframe.on('load', function() {
337 iframe.on('load', function() {
338 // Workaround needed by Firefox, to properly render svg inside
338 // Workaround needed by Firefox, to properly render svg inside
339 // iframes, see http://stackoverflow.com/questions/10177190/
339 // iframes, see http://stackoverflow.com/questions/10177190/
340 // svg-dynamically-added-to-iframe-does-not-render-correctly
340 // svg-dynamically-added-to-iframe-does-not-render-correctly
341 this.contentDocument.open();
341 this.contentDocument.open();
342
342
343 // Insert the subarea into the iframe
343 // Insert the subarea into the iframe
344 // We must directly write the html. When using Jquery's append
344 // We must directly write the html. When using Jquery's append
345 // method, javascript is evaluated in the parent document and
345 // method, javascript is evaluated in the parent document and
346 // not in the iframe document.
346 // not in the iframe document. At this point, subarea doesn't
347 // contain any user content.
347 this.contentDocument.write(subarea.html());
348 this.contentDocument.write(subarea.html());
348
349
349 this.contentDocument.close();
350 this.contentDocument.close();
350
351
351 var body = this.contentDocument.body;
352 var body = this.contentDocument.body;
352 // Adjust the iframe height automatically
353 // Adjust the iframe height automatically
353 iframe.height(body.scrollHeight + 'px');
354 iframe.height(body.scrollHeight + 'px');
354 });
355 });
355
356
356 // Elements should be appended to the inner subarea and not to the
357 // Elements should be appended to the inner subarea and not to the
357 // iframe
358 // iframe
358 iframe.append = function(that) {
359 iframe.append = function(that) {
359 subarea.append(that);
360 subarea.append(that);
360 };
361 };
361
362
362 return iframe;
363 return iframe;
363 } else {
364 } else {
364 return subarea;
365 return subarea;
365 }
366 }
366 }
367 }
367
368
368
369
369 OutputArea.prototype._append_javascript_error = function (err, element) {
370 OutputArea.prototype._append_javascript_error = function (err, element) {
370 // display a message when a javascript error occurs in display output
371 // display a message when a javascript error occurs in display output
371 var msg = "Javascript error adding output!"
372 var msg = "Javascript error adding output!"
372 if ( element === undefined ) return;
373 if ( element === undefined ) return;
373 element.append(
374 element
374 $('<div/>').html(msg + "<br/>" +
375 .append($('<div/>').text(msg).addClass('js-error'))
375 err.toString() +
376 .append($('<div/>').text(err.toString()).addClass('js-error'))
376 '<br/>See your browser Javascript console for more details.'
377 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
377 ).addClass('js-error')
378 );
379 };
378 };
380
379
381 OutputArea.prototype._safe_append = function (toinsert) {
380 OutputArea.prototype._safe_append = function (toinsert) {
382 // safely append an item to the document
381 // safely append an item to the document
383 // this is an object created by user code,
382 // this is an object created by user code,
384 // and may have errors, which should not be raised
383 // and may have errors, which should not be raised
385 // under any circumstances.
384 // under any circumstances.
386 try {
385 try {
387 this.element.append(toinsert);
386 this.element.append(toinsert);
388 } catch(err) {
387 } catch(err) {
389 console.log(err);
388 console.log(err);
390 // Create an actual output_area and output_subarea, which creates
389 // Create an actual output_area and output_subarea, which creates
391 // the prompt area and the proper indentation.
390 // the prompt area and the proper indentation.
392 var toinsert = this.create_output_area();
391 var toinsert = this.create_output_area();
393 var subarea = $('<div/>').addClass('output_subarea');
392 var subarea = $('<div/>').addClass('output_subarea');
394 toinsert.append(subarea);
393 toinsert.append(subarea);
395 this._append_javascript_error(err, subarea);
394 this._append_javascript_error(err, subarea);
396 this.element.append(toinsert);
395 this.element.append(toinsert);
397 }
396 }
398 };
397 };
399
398
400
399
401 OutputArea.prototype.append_pyout = function (json) {
400 OutputArea.prototype.append_pyout = function (json) {
402 var n = json.prompt_number || ' ';
401 var n = json.prompt_number || ' ';
403 var toinsert = this.create_output_area();
402 var toinsert = this.create_output_area();
404 if (this.prompt_area) {
403 if (this.prompt_area) {
405 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
404 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
406 }
405 }
407 this.append_mime_type(json, toinsert);
406 this.append_mime_type(json, toinsert);
408 this._safe_append(toinsert);
407 this._safe_append(toinsert);
409 // If we just output latex, typeset it.
408 // If we just output latex, typeset it.
410 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
409 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
411 this.typeset();
410 this.typeset();
412 }
411 }
413 };
412 };
414
413
415
414
416 OutputArea.prototype.append_pyerr = function (json) {
415 OutputArea.prototype.append_pyerr = function (json) {
417 var tb = json.traceback;
416 var tb = json.traceback;
418 if (tb !== undefined && tb.length > 0) {
417 if (tb !== undefined && tb.length > 0) {
419 var s = '';
418 var s = '';
420 var len = tb.length;
419 var len = tb.length;
421 for (var i=0; i<len; i++) {
420 for (var i=0; i<len; i++) {
422 s = s + tb[i] + '\n';
421 s = s + tb[i] + '\n';
423 }
422 }
424 s = s + '\n';
423 s = s + '\n';
425 var toinsert = this.create_output_area();
424 var toinsert = this.create_output_area();
426 this.append_text(s, {}, toinsert);
425 this.append_text(s, {}, toinsert);
427 this._safe_append(toinsert);
426 this._safe_append(toinsert);
428 }
427 }
429 };
428 };
430
429
431
430
432 OutputArea.prototype.append_stream = function (json) {
431 OutputArea.prototype.append_stream = function (json) {
433 // temporary fix: if stream undefined (json file written prior to this patch),
432 // temporary fix: if stream undefined (json file written prior to this patch),
434 // default to most likely stdout:
433 // default to most likely stdout:
435 if (json.stream == undefined){
434 if (json.stream == undefined){
436 json.stream = 'stdout';
435 json.stream = 'stdout';
437 }
436 }
438 var text = json.text;
437 var text = json.text;
439 var subclass = "output_"+json.stream;
438 var subclass = "output_"+json.stream;
440 if (this.outputs.length > 0){
439 if (this.outputs.length > 0){
441 // have at least one output to consider
440 // have at least one output to consider
442 var last = this.outputs[this.outputs.length-1];
441 var last = this.outputs[this.outputs.length-1];
443 if (last.output_type == 'stream' && json.stream == last.stream){
442 if (last.output_type == 'stream' && json.stream == last.stream){
444 // latest output was in the same stream,
443 // latest output was in the same stream,
445 // so append directly into its pre tag
444 // so append directly into its pre tag
446 // escape ANSI & HTML specials:
445 // escape ANSI & HTML specials:
447 var pre = this.element.find('div.'+subclass).last().find('pre');
446 var pre = this.element.find('div.'+subclass).last().find('pre');
448 var html = utils.fixCarriageReturn(
447 var html = utils.fixCarriageReturn(
449 pre.html() + utils.fixConsole(text));
448 pre.html() + utils.fixConsole(text));
449 // The only user content injected with with this HTML call is
450 // escaped by the fixConsole() method.
450 pre.html(html);
451 pre.html(html);
451 return;
452 return;
452 }
453 }
453 }
454 }
454
455
455 if (!text.replace("\r", "")) {
456 if (!text.replace("\r", "")) {
456 // text is nothing (empty string, \r, etc.)
457 // text is nothing (empty string, \r, etc.)
457 // so don't append any elements, which might add undesirable space
458 // so don't append any elements, which might add undesirable space
458 return;
459 return;
459 }
460 }
460
461
461 // If we got here, attach a new div
462 // If we got here, attach a new div
462 var toinsert = this.create_output_area();
463 var toinsert = this.create_output_area();
463 this.append_text(text, {}, toinsert, "output_stream "+subclass);
464 this.append_text(text, {}, toinsert, "output_stream "+subclass);
464 this._safe_append(toinsert);
465 this._safe_append(toinsert);
465 };
466 };
466
467
467
468
468 OutputArea.prototype.append_display_data = function (json) {
469 OutputArea.prototype.append_display_data = function (json) {
469 var toinsert = this.create_output_area();
470 var toinsert = this.create_output_area();
470 if (this.append_mime_type(json, toinsert)) {
471 if (this.append_mime_type(json, toinsert)) {
471 this._safe_append(toinsert);
472 this._safe_append(toinsert);
472 // If we just output latex, typeset it.
473 // If we just output latex, typeset it.
473 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
474 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
474 this.typeset();
475 this.typeset();
475 }
476 }
476 }
477 }
477 };
478 };
478
479
479
480
480 OutputArea.safe_outputs = {
481 OutputArea.safe_outputs = {
481 'text/plain' : true,
482 'text/plain' : true,
482 'image/png' : true,
483 'image/png' : true,
483 'image/jpeg' : true
484 'image/jpeg' : true
484 };
485 };
485
486
486 OutputArea.prototype.append_mime_type = function (json, element) {
487 OutputArea.prototype.append_mime_type = function (json, element) {
487 for (var type_i in OutputArea.display_order) {
488 for (var type_i in OutputArea.display_order) {
488 var type = OutputArea.display_order[type_i];
489 var type = OutputArea.display_order[type_i];
489 var append = OutputArea.append_map[type];
490 var append = OutputArea.append_map[type];
490 if ((json[type] !== undefined) && append) {
491 if ((json[type] !== undefined) && append) {
491 if (!this.trusted && !OutputArea.safe_outputs[type]) {
492 if (!this.trusted && !OutputArea.safe_outputs[type]) {
492 // not trusted show warning and do not display
493 // not trusted show warning and do not display
493 var content = {
494 var content = {
494 text : "Untrusted " + type + " output ignored.",
495 text : "Untrusted " + type + " output ignored.",
495 stream : "stderr"
496 stream : "stderr"
496 }
497 }
497 this.append_stream(content);
498 this.append_stream(content);
498 continue;
499 continue;
499 }
500 }
500 var md = json.metadata || {};
501 var md = json.metadata || {};
501 var toinsert = append.apply(this, [json[type], md, element]);
502 var toinsert = append.apply(this, [json[type], md, element]);
502 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
503 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
503 return true;
504 return true;
504 }
505 }
505 }
506 }
506 return false;
507 return false;
507 };
508 };
508
509
509
510
510 OutputArea.prototype.append_html = function (html, md, element) {
511 OutputArea.prototype.append_html = function (html, md, element) {
511 var type = 'text/html';
512 var type = 'text/html';
512 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
513 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
513 IPython.keyboard_manager.register_events(toinsert);
514 IPython.keyboard_manager.register_events(toinsert);
514 toinsert.append(html);
515 toinsert.append(html);
515 element.append(toinsert);
516 element.append(toinsert);
516 return toinsert;
517 return toinsert;
517 };
518 };
518
519
519
520
520 OutputArea.prototype.append_javascript = function (js, md, element) {
521 OutputArea.prototype.append_javascript = function (js, md, element) {
521 // We just eval the JS code, element appears in the local scope.
522 // We just eval the JS code, element appears in the local scope.
522 var type = 'application/javascript';
523 var type = 'application/javascript';
523 var toinsert = this.create_output_subarea(md, "output_javascript", type);
524 var toinsert = this.create_output_subarea(md, "output_javascript", type);
524 IPython.keyboard_manager.register_events(toinsert);
525 IPython.keyboard_manager.register_events(toinsert);
525 element.append(toinsert);
526 element.append(toinsert);
526 // FIXME TODO : remove `container element for 3.0`
527 // FIXME TODO : remove `container element for 3.0`
527 //backward compat, js should be eval'ed in a context where `container` is defined.
528 //backward compat, js should be eval'ed in a context where `container` is defined.
528 var container = element;
529 var container = element;
529 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
530 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
530 // end backward compat
531 // end backward compat
531 try {
532 try {
532 eval(js);
533 eval(js);
533 } catch(err) {
534 } catch(err) {
534 console.log(err);
535 console.log(err);
535 this._append_javascript_error(err, toinsert);
536 this._append_javascript_error(err, toinsert);
536 }
537 }
537 return toinsert;
538 return toinsert;
538 };
539 };
539
540
540
541
541 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
542 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
542 var type = 'text/plain';
543 var type = 'text/plain';
543 var toinsert = this.create_output_subarea(md, "output_text", type);
544 var toinsert = this.create_output_subarea(md, "output_text", type);
544 // escape ANSI & HTML specials in plaintext:
545 // escape ANSI & HTML specials in plaintext:
545 data = utils.fixConsole(data);
546 data = utils.fixConsole(data);
546 data = utils.fixCarriageReturn(data);
547 data = utils.fixCarriageReturn(data);
547 data = utils.autoLinkUrls(data);
548 data = utils.autoLinkUrls(data);
548 if (extra_class){
549 if (extra_class){
549 toinsert.addClass(extra_class);
550 toinsert.addClass(extra_class);
550 }
551 }
552 // The only user content injected with with this HTML call is
553 // escaped by the fixConsole() method.
551 toinsert.append($("<pre/>").html(data));
554 toinsert.append($("<pre/>").html(data));
552 element.append(toinsert);
555 element.append(toinsert);
553 return toinsert;
556 return toinsert;
554 };
557 };
555
558
556
559
557 OutputArea.prototype.append_svg = function (svg, md, element) {
560 OutputArea.prototype.append_svg = function (svg, md, element) {
558 var type = 'image/svg+xml';
561 var type = 'image/svg+xml';
559 var toinsert = this.create_output_subarea(md, "output_svg", type);
562 var toinsert = this.create_output_subarea(md, "output_svg", type);
560 toinsert.append(svg);
563 toinsert.append(svg);
561 element.append(toinsert);
564 element.append(toinsert);
562 return toinsert;
565 return toinsert;
563 };
566 };
564
567
565
568
566 OutputArea.prototype._dblclick_to_reset_size = function (img) {
569 OutputArea.prototype._dblclick_to_reset_size = function (img) {
567 // schedule wrapping image in resizable after a delay,
570 // schedule wrapping image in resizable after a delay,
568 // so we don't end up calling resize on a zero-size object
571 // so we don't end up calling resize on a zero-size object
569 var that = this;
572 var that = this;
570 setTimeout(function () {
573 setTimeout(function () {
571 var h0 = img.height();
574 var h0 = img.height();
572 var w0 = img.width();
575 var w0 = img.width();
573 if (!(h0 && w0)) {
576 if (!(h0 && w0)) {
574 // zero size, schedule another timeout
577 // zero size, schedule another timeout
575 that._dblclick_to_reset_size(img);
578 that._dblclick_to_reset_size(img);
576 return;
579 return;
577 }
580 }
578 img.resizable({
581 img.resizable({
579 aspectRatio: true,
582 aspectRatio: true,
580 autoHide: true
583 autoHide: true
581 });
584 });
582 img.dblclick(function () {
585 img.dblclick(function () {
583 // resize wrapper & image together for some reason:
586 // resize wrapper & image together for some reason:
584 img.parent().height(h0);
587 img.parent().height(h0);
585 img.height(h0);
588 img.height(h0);
586 img.parent().width(w0);
589 img.parent().width(w0);
587 img.width(w0);
590 img.width(w0);
588 });
591 });
589 }, 250);
592 }, 250);
590 };
593 };
591
594
592 var set_width_height = function (img, md, mime) {
595 var set_width_height = function (img, md, mime) {
593 // set width and height of an img element from metadata
596 // set width and height of an img element from metadata
594 var height = _get_metadata_key(md, 'height', mime);
597 var height = _get_metadata_key(md, 'height', mime);
595 if (height !== undefined) img.attr('height', height);
598 if (height !== undefined) img.attr('height', height);
596 var width = _get_metadata_key(md, 'width', mime);
599 var width = _get_metadata_key(md, 'width', mime);
597 if (width !== undefined) img.attr('width', width);
600 if (width !== undefined) img.attr('width', width);
598 };
601 };
599
602
600 OutputArea.prototype.append_png = function (png, md, element) {
603 OutputArea.prototype.append_png = function (png, md, element) {
601 var type = 'image/png';
604 var type = 'image/png';
602 var toinsert = this.create_output_subarea(md, "output_png", type);
605 var toinsert = this.create_output_subarea(md, "output_png", type);
603 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
606 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
604 set_width_height(img, md, 'image/png');
607 set_width_height(img, md, 'image/png');
605 this._dblclick_to_reset_size(img);
608 this._dblclick_to_reset_size(img);
606 toinsert.append(img);
609 toinsert.append(img);
607 element.append(toinsert);
610 element.append(toinsert);
608 return toinsert;
611 return toinsert;
609 };
612 };
610
613
611
614
612 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
615 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
613 var type = 'image/jpeg';
616 var type = 'image/jpeg';
614 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
617 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
615 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
618 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
616 set_width_height(img, md, 'image/jpeg');
619 set_width_height(img, md, 'image/jpeg');
617 this._dblclick_to_reset_size(img);
620 this._dblclick_to_reset_size(img);
618 toinsert.append(img);
621 toinsert.append(img);
619 element.append(toinsert);
622 element.append(toinsert);
620 return toinsert;
623 return toinsert;
621 };
624 };
622
625
623
626
624 OutputArea.prototype.append_pdf = function (pdf, md, element) {
627 OutputArea.prototype.append_pdf = function (pdf, md, element) {
625 var type = 'application/pdf';
628 var type = 'application/pdf';
626 var toinsert = this.create_output_subarea(md, "output_pdf", type);
629 var toinsert = this.create_output_subarea(md, "output_pdf", type);
627 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
630 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
628 a.attr('target', '_blank');
631 a.attr('target', '_blank');
629 a.text('View PDF')
632 a.text('View PDF')
630 toinsert.append(a);
633 toinsert.append(a);
631 element.append(toinsert);
634 element.append(toinsert);
632 return toinsert;
635 return toinsert;
633 }
636 }
634
637
635 OutputArea.prototype.append_latex = function (latex, md, element) {
638 OutputArea.prototype.append_latex = function (latex, md, element) {
636 // This method cannot do the typesetting because the latex first has to
639 // This method cannot do the typesetting because the latex first has to
637 // be on the page.
640 // be on the page.
638 var type = 'text/latex';
641 var type = 'text/latex';
639 var toinsert = this.create_output_subarea(md, "output_latex", type);
642 var toinsert = this.create_output_subarea(md, "output_latex", type);
640 toinsert.append(latex);
643 toinsert.append(latex);
641 element.append(toinsert);
644 element.append(toinsert);
642 return toinsert;
645 return toinsert;
643 };
646 };
644
647
645
648
646 OutputArea.prototype.append_raw_input = function (msg) {
649 OutputArea.prototype.append_raw_input = function (msg) {
647 var that = this;
650 var that = this;
648 this.expand();
651 this.expand();
649 var content = msg.content;
652 var content = msg.content;
650 var area = this.create_output_area();
653 var area = this.create_output_area();
651
654
652 // disable any other raw_inputs, if they are left around
655 // disable any other raw_inputs, if they are left around
653 $("div.output_subarea.raw_input").remove();
656 $("div.output_subarea.raw_input").remove();
654
657
655 area.append(
658 area.append(
656 $("<div/>")
659 $("<div/>")
657 .addClass("box-flex1 output_subarea raw_input")
660 .addClass("box-flex1 output_subarea raw_input")
658 .append(
661 .append(
659 $("<span/>")
662 $("<span/>")
660 .addClass("input_prompt")
663 .addClass("input_prompt")
661 .text(content.prompt)
664 .text(content.prompt)
662 )
665 )
663 .append(
666 .append(
664 $("<input/>")
667 $("<input/>")
665 .addClass("raw_input")
668 .addClass("raw_input")
666 .attr('type', 'text')
669 .attr('type', 'text')
667 .attr("size", 47)
670 .attr("size", 47)
668 .keydown(function (event, ui) {
671 .keydown(function (event, ui) {
669 // make sure we submit on enter,
672 // make sure we submit on enter,
670 // and don't re-execute the *cell* on shift-enter
673 // and don't re-execute the *cell* on shift-enter
671 if (event.which === utils.keycodes.ENTER) {
674 if (event.which === utils.keycodes.ENTER) {
672 that._submit_raw_input();
675 that._submit_raw_input();
673 return false;
676 return false;
674 }
677 }
675 })
678 })
676 )
679 )
677 );
680 );
678
681
679 this.element.append(area);
682 this.element.append(area);
680 var raw_input = area.find('input.raw_input');
683 var raw_input = area.find('input.raw_input');
681 // Register events that enable/disable the keyboard manager while raw
684 // Register events that enable/disable the keyboard manager while raw
682 // input is focused.
685 // input is focused.
683 IPython.keyboard_manager.register_events(raw_input);
686 IPython.keyboard_manager.register_events(raw_input);
684 // Note, the following line used to read raw_input.focus().focus().
687 // Note, the following line used to read raw_input.focus().focus().
685 // This seemed to be needed otherwise only the cell would be focused.
688 // This seemed to be needed otherwise only the cell would be focused.
686 // But with the modal UI, this seems to work fine with one call to focus().
689 // But with the modal UI, this seems to work fine with one call to focus().
687 raw_input.focus();
690 raw_input.focus();
688 }
691 }
689
692
690 OutputArea.prototype._submit_raw_input = function (evt) {
693 OutputArea.prototype._submit_raw_input = function (evt) {
691 var container = this.element.find("div.raw_input");
694 var container = this.element.find("div.raw_input");
692 var theprompt = container.find("span.input_prompt");
695 var theprompt = container.find("span.input_prompt");
693 var theinput = container.find("input.raw_input");
696 var theinput = container.find("input.raw_input");
694 var value = theinput.val();
697 var value = theinput.val();
695 var content = {
698 var content = {
696 output_type : 'stream',
699 output_type : 'stream',
697 name : 'stdout',
700 name : 'stdout',
698 text : theprompt.text() + value + '\n'
701 text : theprompt.text() + value + '\n'
699 }
702 }
700 // remove form container
703 // remove form container
701 container.parent().remove();
704 container.parent().remove();
702 // replace with plaintext version in stdout
705 // replace with plaintext version in stdout
703 this.append_output(content, false);
706 this.append_output(content, false);
704 $([IPython.events]).trigger('send_input_reply.Kernel', value);
707 $([IPython.events]).trigger('send_input_reply.Kernel', value);
705 }
708 }
706
709
707
710
708 OutputArea.prototype.handle_clear_output = function (msg) {
711 OutputArea.prototype.handle_clear_output = function (msg) {
709 // msg spec v4 had stdout, stderr, display keys
712 // msg spec v4 had stdout, stderr, display keys
710 // v4.1 replaced these with just wait
713 // v4.1 replaced these with just wait
711 // The default behavior is the same (stdout=stderr=display=True, wait=False),
714 // The default behavior is the same (stdout=stderr=display=True, wait=False),
712 // so v4 messages will still be properly handled,
715 // so v4 messages will still be properly handled,
713 // except for the rarely used clearing less than all output.
716 // except for the rarely used clearing less than all output.
714 this.clear_output(msg.content.wait || false);
717 this.clear_output(msg.content.wait || false);
715 };
718 };
716
719
717
720
718 OutputArea.prototype.clear_output = function(wait) {
721 OutputArea.prototype.clear_output = function(wait) {
719 if (wait) {
722 if (wait) {
720
723
721 // If a clear is queued, clear before adding another to the queue.
724 // If a clear is queued, clear before adding another to the queue.
722 if (this.clear_queued) {
725 if (this.clear_queued) {
723 this.clear_output(false);
726 this.clear_output(false);
724 };
727 };
725
728
726 this.clear_queued = true;
729 this.clear_queued = true;
727 } else {
730 } else {
728
731
729 // Fix the output div's height if the clear_output is waiting for
732 // Fix the output div's height if the clear_output is waiting for
730 // new output (it is being used in an animation).
733 // new output (it is being used in an animation).
731 if (this.clear_queued) {
734 if (this.clear_queued) {
732 var height = this.element.height();
735 var height = this.element.height();
733 this.element.height(height);
736 this.element.height(height);
734 this.clear_queued = false;
737 this.clear_queued = false;
735 }
738 }
736
739
737 // clear all, no need for logic
740 // clear all, no need for logic
738 this.element.html("");
741 this.element.html("");
739 this.outputs = [];
742 this.outputs = [];
740 this.trusted = true;
743 this.trusted = true;
741 this.unscroll_area();
744 this.unscroll_area();
742 return;
745 return;
743 };
746 };
744 };
747 };
745
748
746
749
747 // JSON serialization
750 // JSON serialization
748
751
749 OutputArea.prototype.fromJSON = function (outputs) {
752 OutputArea.prototype.fromJSON = function (outputs) {
750 var len = outputs.length;
753 var len = outputs.length;
751 var data;
754 var data;
752
755
753 for (var i=0; i<len; i++) {
756 for (var i=0; i<len; i++) {
754 data = outputs[i];
757 data = outputs[i];
755 var msg_type = data.output_type;
758 var msg_type = data.output_type;
756 if (msg_type === "display_data" || msg_type === "pyout") {
759 if (msg_type === "display_data" || msg_type === "pyout") {
757 // convert short keys to mime keys
760 // convert short keys to mime keys
758 // TODO: remove mapping of short keys when we update to nbformat 4
761 // TODO: remove mapping of short keys when we update to nbformat 4
759 data = this.rename_keys(data, OutputArea.mime_map_r);
762 data = this.rename_keys(data, OutputArea.mime_map_r);
760 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
763 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
761 }
764 }
762
765
763 this.append_output(data);
766 this.append_output(data);
764 }
767 }
765 };
768 };
766
769
767
770
768 OutputArea.prototype.toJSON = function () {
771 OutputArea.prototype.toJSON = function () {
769 var outputs = [];
772 var outputs = [];
770 var len = this.outputs.length;
773 var len = this.outputs.length;
771 var data;
774 var data;
772 for (var i=0; i<len; i++) {
775 for (var i=0; i<len; i++) {
773 data = this.outputs[i];
776 data = this.outputs[i];
774 var msg_type = data.output_type;
777 var msg_type = data.output_type;
775 if (msg_type === "display_data" || msg_type === "pyout") {
778 if (msg_type === "display_data" || msg_type === "pyout") {
776 // convert mime keys to short keys
779 // convert mime keys to short keys
777 data = this.rename_keys(data, OutputArea.mime_map);
780 data = this.rename_keys(data, OutputArea.mime_map);
778 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
781 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
779 }
782 }
780 outputs[i] = data;
783 outputs[i] = data;
781 }
784 }
782 return outputs;
785 return outputs;
783 };
786 };
784
787
785 /**
788 /**
786 * Class properties
789 * Class properties
787 **/
790 **/
788
791
789 /**
792 /**
790 * Threshold to trigger autoscroll when the OutputArea is resized,
793 * Threshold to trigger autoscroll when the OutputArea is resized,
791 * typically when new outputs are added.
794 * typically when new outputs are added.
792 *
795 *
793 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
796 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
794 * unless it is < 0, in which case autoscroll will never be triggered
797 * unless it is < 0, in which case autoscroll will never be triggered
795 *
798 *
796 * @property auto_scroll_threshold
799 * @property auto_scroll_threshold
797 * @type Number
800 * @type Number
798 * @default 100
801 * @default 100
799 *
802 *
800 **/
803 **/
801 OutputArea.auto_scroll_threshold = 100;
804 OutputArea.auto_scroll_threshold = 100;
802
805
803 /**
806 /**
804 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
807 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
805 * shorter than this are never scrolled.
808 * shorter than this are never scrolled.
806 *
809 *
807 * @property minimum_scroll_threshold
810 * @property minimum_scroll_threshold
808 * @type Number
811 * @type Number
809 * @default 20
812 * @default 20
810 *
813 *
811 **/
814 **/
812 OutputArea.minimum_scroll_threshold = 20;
815 OutputArea.minimum_scroll_threshold = 20;
813
816
814
817
815
818
816 OutputArea.mime_map = {
819 OutputArea.mime_map = {
817 "text/plain" : "text",
820 "text/plain" : "text",
818 "text/html" : "html",
821 "text/html" : "html",
819 "image/svg+xml" : "svg",
822 "image/svg+xml" : "svg",
820 "image/png" : "png",
823 "image/png" : "png",
821 "image/jpeg" : "jpeg",
824 "image/jpeg" : "jpeg",
822 "application/pdf" : "pdf",
825 "application/pdf" : "pdf",
823 "text/latex" : "latex",
826 "text/latex" : "latex",
824 "application/json" : "json",
827 "application/json" : "json",
825 "application/javascript" : "javascript",
828 "application/javascript" : "javascript",
826 };
829 };
827
830
828 OutputArea.mime_map_r = {
831 OutputArea.mime_map_r = {
829 "text" : "text/plain",
832 "text" : "text/plain",
830 "html" : "text/html",
833 "html" : "text/html",
831 "svg" : "image/svg+xml",
834 "svg" : "image/svg+xml",
832 "png" : "image/png",
835 "png" : "image/png",
833 "jpeg" : "image/jpeg",
836 "jpeg" : "image/jpeg",
834 "pdf" : "application/pdf",
837 "pdf" : "application/pdf",
835 "latex" : "text/latex",
838 "latex" : "text/latex",
836 "json" : "application/json",
839 "json" : "application/json",
837 "javascript" : "application/javascript",
840 "javascript" : "application/javascript",
838 };
841 };
839
842
840 OutputArea.display_order = [
843 OutputArea.display_order = [
841 'application/javascript',
844 'application/javascript',
842 'text/html',
845 'text/html',
843 'text/latex',
846 'text/latex',
844 'image/svg+xml',
847 'image/svg+xml',
845 'image/png',
848 'image/png',
846 'image/jpeg',
849 'image/jpeg',
847 'application/pdf',
850 'application/pdf',
848 'text/plain'
851 'text/plain'
849 ];
852 ];
850
853
851 OutputArea.append_map = {
854 OutputArea.append_map = {
852 "text/plain" : OutputArea.prototype.append_text,
855 "text/plain" : OutputArea.prototype.append_text,
853 "text/html" : OutputArea.prototype.append_html,
856 "text/html" : OutputArea.prototype.append_html,
854 "image/svg+xml" : OutputArea.prototype.append_svg,
857 "image/svg+xml" : OutputArea.prototype.append_svg,
855 "image/png" : OutputArea.prototype.append_png,
858 "image/png" : OutputArea.prototype.append_png,
856 "image/jpeg" : OutputArea.prototype.append_jpeg,
859 "image/jpeg" : OutputArea.prototype.append_jpeg,
857 "text/latex" : OutputArea.prototype.append_latex,
860 "text/latex" : OutputArea.prototype.append_latex,
858 "application/javascript" : OutputArea.prototype.append_javascript,
861 "application/javascript" : OutputArea.prototype.append_javascript,
859 "application/pdf" : OutputArea.prototype.append_pdf
862 "application/pdf" : OutputArea.prototype.append_pdf
860 };
863 };
861
864
862 IPython.OutputArea = OutputArea;
865 IPython.OutputArea = OutputArea;
863
866
864 return IPython;
867 return IPython;
865
868
866 }(IPython));
869 }(IPython));
@@ -1,176 +1,178 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 // Pager
9 // Pager
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var Pager = function (pager_selector, pager_splitter_selector) {
17 var Pager = function (pager_selector, pager_splitter_selector) {
18 this.pager_element = $(pager_selector);
18 this.pager_element = $(pager_selector);
19 this.pager_button_area = $('#pager_button_area');
19 this.pager_button_area = $('#pager_button_area');
20 var that = this;
20 var that = this;
21 this.percentage_height = 0.40;
21 this.percentage_height = 0.40;
22 this.pager_splitter_element = $(pager_splitter_selector)
22 this.pager_splitter_element = $(pager_splitter_selector)
23 .draggable({
23 .draggable({
24 containment: 'window',
24 containment: 'window',
25 axis:'y',
25 axis:'y',
26 helper: null ,
26 helper: null ,
27 drag: function(event, ui) {
27 drag: function(event, ui) {
28 // recalculate the amount of space the pager should take
28 // recalculate the amount of space the pager should take
29 var pheight = ($(document.body).height()-event.clientY-4);
29 var pheight = ($(document.body).height()-event.clientY-4);
30 var downprct = pheight/IPython.layout_manager.app_height();
30 var downprct = pheight/IPython.layout_manager.app_height();
31 downprct = Math.min(0.9, downprct);
31 downprct = Math.min(0.9, downprct);
32 if (downprct < 0.1) {
32 if (downprct < 0.1) {
33 that.percentage_height = 0.1;
33 that.percentage_height = 0.1;
34 that.collapse({'duration':0});
34 that.collapse({'duration':0});
35 } else if (downprct > 0.2) {
35 } else if (downprct > 0.2) {
36 that.percentage_height = downprct;
36 that.percentage_height = downprct;
37 that.expand({'duration':0});
37 that.expand({'duration':0});
38 }
38 }
39 IPython.layout_manager.do_resize();
39 IPython.layout_manager.do_resize();
40 }
40 }
41 });
41 });
42 this.expanded = false;
42 this.expanded = false;
43 this.style();
43 this.style();
44 this.create_button_area();
44 this.create_button_area();
45 this.bind_events();
45 this.bind_events();
46 };
46 };
47
47
48 Pager.prototype.create_button_area = function(){
48 Pager.prototype.create_button_area = function(){
49 var that = this;
49 var that = this;
50 this.pager_button_area.append(
50 this.pager_button_area.append(
51 $('<a>').attr('role', "button")
51 $('<a>').attr('role', "button")
52 .attr('title',"Open the pager in an external window")
52 .attr('title',"Open the pager in an external window")
53 .addClass('ui-button')
53 .addClass('ui-button')
54 .click(function(){that.detach()})
54 .click(function(){that.detach()})
55 .attr('style','position: absolute; right: 20px;')
55 .attr('style','position: absolute; right: 20px;')
56 .append(
56 .append(
57 $('<span>').addClass("ui-icon ui-icon-extlink")
57 $('<span>').addClass("ui-icon ui-icon-extlink")
58 )
58 )
59 )
59 )
60 this.pager_button_area.append(
60 this.pager_button_area.append(
61 $('<a>').attr('role', "button")
61 $('<a>').attr('role', "button")
62 .attr('title',"Close the pager")
62 .attr('title',"Close the pager")
63 .addClass('ui-button')
63 .addClass('ui-button')
64 .click(function(){that.collapse()})
64 .click(function(){that.collapse()})
65 .attr('style','position: absolute; right: 5px;')
65 .attr('style','position: absolute; right: 5px;')
66 .append(
66 .append(
67 $('<span>').addClass("ui-icon ui-icon-close")
67 $('<span>').addClass("ui-icon ui-icon-close")
68 )
68 )
69 )
69 )
70 };
70 };
71
71
72 Pager.prototype.style = function () {
72 Pager.prototype.style = function () {
73 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
73 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
74 this.pager_element.addClass('border-box-sizing');
74 this.pager_element.addClass('border-box-sizing');
75 this.pager_element.find(".container").addClass('border-box-sizing');
75 this.pager_element.find(".container").addClass('border-box-sizing');
76 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
76 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
77 };
77 };
78
78
79
79
80 Pager.prototype.bind_events = function () {
80 Pager.prototype.bind_events = function () {
81 var that = this;
81 var that = this;
82
82
83 this.pager_element.bind('collapse_pager', function (event, extrap) {
83 this.pager_element.bind('collapse_pager', function (event, extrap) {
84 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
84 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
85 that.pager_element.hide(time);
85 that.pager_element.hide(time);
86 });
86 });
87
87
88 this.pager_element.bind('expand_pager', function (event, extrap) {
88 this.pager_element.bind('expand_pager', function (event, extrap) {
89 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
89 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
90 that.pager_element.show(time);
90 that.pager_element.show(time);
91 });
91 });
92
92
93 this.pager_splitter_element.hover(
93 this.pager_splitter_element.hover(
94 function () {
94 function () {
95 that.pager_splitter_element.addClass('ui-state-hover');
95 that.pager_splitter_element.addClass('ui-state-hover');
96 },
96 },
97 function () {
97 function () {
98 that.pager_splitter_element.removeClass('ui-state-hover');
98 that.pager_splitter_element.removeClass('ui-state-hover');
99 }
99 }
100 );
100 );
101
101
102 this.pager_splitter_element.click(function () {
102 this.pager_splitter_element.click(function () {
103 that.toggle();
103 that.toggle();
104 });
104 });
105
105
106 $([IPython.events]).on('open_with_text.Pager', function (event, data) {
106 $([IPython.events]).on('open_with_text.Pager', function (event, data) {
107 if (data.text.trim() !== '') {
107 if (data.text.trim() !== '') {
108 that.clear();
108 that.clear();
109 that.expand();
109 that.expand();
110 that.append_text(data.text);
110 that.append_text(data.text);
111 };
111 };
112 });
112 });
113 };
113 };
114
114
115
115
116 Pager.prototype.collapse = function (extrap) {
116 Pager.prototype.collapse = function (extrap) {
117 if (this.expanded === true) {
117 if (this.expanded === true) {
118 this.expanded = false;
118 this.expanded = false;
119 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
119 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
120 };
120 };
121 };
121 };
122
122
123
123
124 Pager.prototype.expand = function (extrap) {
124 Pager.prototype.expand = function (extrap) {
125 if (this.expanded !== true) {
125 if (this.expanded !== true) {
126 this.expanded = true;
126 this.expanded = true;
127 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
127 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
128 };
128 };
129 };
129 };
130
130
131
131
132 Pager.prototype.toggle = function () {
132 Pager.prototype.toggle = function () {
133 if (this.expanded === true) {
133 if (this.expanded === true) {
134 this.collapse();
134 this.collapse();
135 } else {
135 } else {
136 this.expand();
136 this.expand();
137 };
137 };
138 };
138 };
139
139
140
140
141 Pager.prototype.clear = function (text) {
141 Pager.prototype.clear = function (text) {
142 this.pager_element.find(".container").empty();
142 this.pager_element.find(".container").empty();
143 };
143 };
144
144
145 Pager.prototype.detach = function(){
145 Pager.prototype.detach = function(){
146 var w = window.open("","_blank");
146 var w = window.open("","_blank");
147 $(w.document.head)
147 $(w.document.head)
148 .append(
148 .append(
149 $('<link>')
149 $('<link>')
150 .attr('rel',"stylesheet")
150 .attr('rel',"stylesheet")
151 .attr('href',"/static/css/notebook.css")
151 .attr('href',"/static/css/notebook.css")
152 .attr('type',"text/css")
152 .attr('type',"text/css")
153 )
153 )
154 .append(
154 .append(
155 $('<title>').text("IPython Pager")
155 $('<title>').text("IPython Pager")
156 );
156 );
157 var pager_body = $(w.document.body);
157 var pager_body = $(w.document.body);
158 pager_body.css('overflow','scroll');
158 pager_body.css('overflow','scroll');
159
159
160 pager_body.append(this.pager_element.clone().children());
160 pager_body.append(this.pager_element.clone().children());
161 w.document.close();
161 w.document.close();
162 this.collapse();
162 this.collapse();
163
163
164 }
164 }
165
165
166 Pager.prototype.append_text = function (text) {
166 Pager.prototype.append_text = function (text) {
167 // The only user content injected with with this HTML call is escaped by
168 // the fixConsole() method.
167 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
169 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
168 };
170 };
169
171
170
172
171 IPython.Pager = Pager;
173 IPython.Pager = Pager;
172
174
173 return IPython;
175 return IPython;
174
176
175 }(IPython));
177 }(IPython));
176
178
@@ -1,560 +1,562 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') {
158 } else if (event.which === key.ESC && event.type === 'keydown') {
159 if (that.code_mirror.options.keyMap === "vim-insert") {
159 if (that.code_mirror.options.keyMap === "vim-insert") {
160 // vim keyMap is active and in insert mode. In this case we leave vim
160 // vim keyMap is active and in insert mode. In this case we leave vim
161 // insert mode, but remain in notebook edit mode.
161 // insert mode, but remain in notebook edit mode.
162 // Let' CM handle this event and prevent global handling.
162 // Let' CM handle this event and prevent global handling.
163 event.stop();
163 event.stop();
164 return false;
164 return false;
165 } else {
165 } else {
166 // vim keyMap is not active. Leave notebook edit mode.
166 // vim keyMap is not active. Leave notebook edit mode.
167 // Don't let CM handle the event, defer to global handling.
167 // Don't let CM handle the event, defer to global handling.
168 return true;
168 return true;
169 }
169 }
170 }
170 }
171 return false;
171 return false;
172 };
172 };
173
173
174 // Cell level actions
174 // Cell level actions
175
175
176 TextCell.prototype.select = function () {
176 TextCell.prototype.select = function () {
177 var cont = IPython.Cell.prototype.select.apply(this);
177 var cont = IPython.Cell.prototype.select.apply(this);
178 if (cont) {
178 if (cont) {
179 if (this.mode === 'edit') {
179 if (this.mode === 'edit') {
180 this.code_mirror.refresh();
180 this.code_mirror.refresh();
181 }
181 }
182 };
182 };
183 return cont;
183 return cont;
184 };
184 };
185
185
186 TextCell.prototype.unrender = function () {
186 TextCell.prototype.unrender = function () {
187 if (this.read_only) return;
187 if (this.read_only) return;
188 var cont = IPython.Cell.prototype.unrender.apply(this);
188 var cont = IPython.Cell.prototype.unrender.apply(this);
189 if (cont) {
189 if (cont) {
190 var text_cell = this.element;
190 var text_cell = this.element;
191 var output = text_cell.find("div.text_cell_render");
191 var output = text_cell.find("div.text_cell_render");
192 output.hide();
192 output.hide();
193 text_cell.find('div.text_cell_input').show();
193 text_cell.find('div.text_cell_input').show();
194 if (this.get_text() === this.placeholder) {
194 if (this.get_text() === this.placeholder) {
195 this.set_text('');
195 this.set_text('');
196 this.refresh();
196 this.refresh();
197 }
197 }
198
198
199 };
199 };
200 return cont;
200 return cont;
201 };
201 };
202
202
203 TextCell.prototype.execute = function () {
203 TextCell.prototype.execute = function () {
204 this.render();
204 this.render();
205 };
205 };
206
206
207 TextCell.prototype.edit_mode = function () {
207 TextCell.prototype.edit_mode = function () {
208 var cont = IPython.Cell.prototype.edit_mode.apply(this);
208 var cont = IPython.Cell.prototype.edit_mode.apply(this);
209 if (cont) {
209 if (cont) {
210 this.unrender();
210 this.unrender();
211 this.focus_editor();
211 this.focus_editor();
212 };
212 };
213 return cont;
213 return cont;
214 }
214 }
215
215
216 /**
216 /**
217 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
217 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
218 * @method get_text
218 * @method get_text
219 * @retrun {string} CodeMirror current text value
219 * @retrun {string} CodeMirror current text value
220 */
220 */
221 TextCell.prototype.get_text = function() {
221 TextCell.prototype.get_text = function() {
222 return this.code_mirror.getValue();
222 return this.code_mirror.getValue();
223 };
223 };
224
224
225 /**
225 /**
226 * @param {string} text - Codemiror text value
226 * @param {string} text - Codemiror text value
227 * @see TextCell#get_text
227 * @see TextCell#get_text
228 * @method set_text
228 * @method set_text
229 * */
229 * */
230 TextCell.prototype.set_text = function(text) {
230 TextCell.prototype.set_text = function(text) {
231 this.code_mirror.setValue(text);
231 this.code_mirror.setValue(text);
232 this.code_mirror.refresh();
232 this.code_mirror.refresh();
233 };
233 };
234
234
235 /**
235 /**
236 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
236 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
237 * @method get_rendered
237 * @method get_rendered
238 * @return {html} html of rendered element
238 * @return {html} html of rendered element
239 * */
239 * */
240 TextCell.prototype.get_rendered = function() {
240 TextCell.prototype.get_rendered = function() {
241 return this.element.find('div.text_cell_render').html();
241 return this.element.find('div.text_cell_render').html();
242 };
242 };
243
243
244 /**
244 /**
245 * @method set_rendered
245 * @method set_rendered
246 */
246 */
247 TextCell.prototype.set_rendered = function(text) {
247 TextCell.prototype.set_rendered = function(text) {
248 this.element.find('div.text_cell_render').html(text);
248 this.element.find('div.text_cell_render').text(text);
249 };
249 };
250
250
251 /**
251 /**
252 * @method at_top
252 * @method at_top
253 * @return {Boolean}
253 * @return {Boolean}
254 */
254 */
255 TextCell.prototype.at_top = function () {
255 TextCell.prototype.at_top = function () {
256 if (this.rendered) {
256 if (this.rendered) {
257 return true;
257 return true;
258 } else {
258 } else {
259 var cursor = this.code_mirror.getCursor();
259 var cursor = this.code_mirror.getCursor();
260 if (cursor.line === 0 && cursor.ch === 0) {
260 if (cursor.line === 0 && cursor.ch === 0) {
261 return true;
261 return true;
262 } else {
262 } else {
263 return false;
263 return false;
264 };
264 };
265 };
265 };
266 };
266 };
267
267
268 /**
268 /**
269 * @method at_bottom
269 * @method at_bottom
270 * @return {Boolean}
270 * @return {Boolean}
271 * */
271 * */
272 TextCell.prototype.at_bottom = function () {
272 TextCell.prototype.at_bottom = function () {
273 if (this.rendered) {
273 if (this.rendered) {
274 return true;
274 return true;
275 } else {
275 } else {
276 var cursor = this.code_mirror.getCursor();
276 var cursor = this.code_mirror.getCursor();
277 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
277 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
278 return true;
278 return true;
279 } else {
279 } else {
280 return false;
280 return false;
281 };
281 };
282 };
282 };
283 };
283 };
284
284
285 /**
285 /**
286 * Create Text cell from JSON
286 * Create Text cell from JSON
287 * @param {json} data - JSON serialized text-cell
287 * @param {json} data - JSON serialized text-cell
288 * @method fromJSON
288 * @method fromJSON
289 */
289 */
290 TextCell.prototype.fromJSON = function (data) {
290 TextCell.prototype.fromJSON = function (data) {
291 IPython.Cell.prototype.fromJSON.apply(this, arguments);
291 IPython.Cell.prototype.fromJSON.apply(this, arguments);
292 if (data.cell_type === this.cell_type) {
292 if (data.cell_type === this.cell_type) {
293 if (data.source !== undefined) {
293 if (data.source !== undefined) {
294 this.set_text(data.source);
294 this.set_text(data.source);
295 // make this value the starting point, so that we can only undo
295 // make this value the starting point, so that we can only undo
296 // to this state, instead of a blank cell
296 // to this state, instead of a blank cell
297 this.code_mirror.clearHistory();
297 this.code_mirror.clearHistory();
298 this.set_rendered(data.rendered || '');
298 this.set_rendered(data.rendered || '');
299 this.rendered = false;
299 this.rendered = false;
300 this.render();
300 this.render();
301 }
301 }
302 }
302 }
303 };
303 };
304
304
305 /** Generate JSON from cell
305 /** Generate JSON from cell
306 * @return {object} cell data serialised to json
306 * @return {object} cell data serialised to json
307 */
307 */
308 TextCell.prototype.toJSON = function () {
308 TextCell.prototype.toJSON = function () {
309 var data = IPython.Cell.prototype.toJSON.apply(this);
309 var data = IPython.Cell.prototype.toJSON.apply(this);
310 data.source = this.get_text();
310 data.source = this.get_text();
311 if (data.source == this.placeholder) {
311 if (data.source == this.placeholder) {
312 data.source = "";
312 data.source = "";
313 }
313 }
314 return data;
314 return data;
315 };
315 };
316
316
317
317
318 /**
318 /**
319 * @class MarkdownCell
319 * @class MarkdownCell
320 * @constructor MarkdownCell
320 * @constructor MarkdownCell
321 * @extends IPython.HTMLCell
321 * @extends IPython.HTMLCell
322 */
322 */
323 var MarkdownCell = function (options) {
323 var MarkdownCell = function (options) {
324 options = this.mergeopt(MarkdownCell, options);
324 options = this.mergeopt(MarkdownCell, options);
325
325
326 this.cell_type = 'markdown';
326 this.cell_type = 'markdown';
327 TextCell.apply(this, [options]);
327 TextCell.apply(this, [options]);
328 };
328 };
329
329
330 MarkdownCell.options_default = {
330 MarkdownCell.options_default = {
331 cm_config: {
331 cm_config: {
332 mode: 'gfm'
332 mode: 'gfm'
333 },
333 },
334 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
334 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
335 }
335 }
336
336
337 MarkdownCell.prototype = new TextCell();
337 MarkdownCell.prototype = new TextCell();
338
338
339 /**
339 /**
340 * @method render
340 * @method render
341 */
341 */
342 MarkdownCell.prototype.render = function () {
342 MarkdownCell.prototype.render = function () {
343 var cont = IPython.TextCell.prototype.render.apply(this);
343 var cont = IPython.TextCell.prototype.render.apply(this);
344 if (cont) {
344 if (cont) {
345 var text = this.get_text();
345 var text = this.get_text();
346 var math = null;
346 var math = null;
347 if (text === "") { text = this.placeholder; }
347 if (text === "") { text = this.placeholder; }
348 var text_and_math = IPython.mathjaxutils.remove_math(text);
348 var text_and_math = IPython.mathjaxutils.remove_math(text);
349 text = text_and_math[0];
349 text = text_and_math[0];
350 math = text_and_math[1];
350 math = text_and_math[1];
351 var html = marked.parser(marked.lexer(text));
351 var html = marked.parser(marked.lexer(text));
352 html = $(IPython.mathjaxutils.replace_math(html, math));
352 html = $(IPython.mathjaxutils.replace_math(html, math));
353 // links in markdown cells should open in new tabs
353 // Links in markdown cells should open in new tabs.
354 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
354 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
355 try {
355 try {
356 this.set_rendered(html);
356 // TODO: This HTML needs to be treated as potentially dangerous
357 // user input.
358 rendered.html(html);
357 } catch (e) {
359 } catch (e) {
358 console.log("Error running Javascript in Markdown:");
360 console.log("Error running Javascript in Markdown:");
359 console.log(e);
361 console.log(e);
360 this.set_rendered($("<div/>").addClass("js-error").html(
362 rendered.empty();
361 "Error rendering Markdown!<br/>" + e.toString())
363 rendered.append(
364 $("<div/>")
365 .append($("<div/>").text('Error rendering Markdown!').addClass("js-error"))
366 .append($("<div/>").text(e.toString()).addClass("js-error"))
362 );
367 );
363 }
368 }
364 this.element.find('div.text_cell_input').hide();
369 this.element.find('div.text_cell_input').hide();
365 this.element.find("div.text_cell_render").show();
370 this.element.find("div.text_cell_render").show();
366 this.typeset()
371 this.typeset()
367 };
372 };
368 return cont;
373 return cont;
369 };
374 };
370
375
371
376
372 // RawCell
377 // RawCell
373
378
374 /**
379 /**
375 * @class RawCell
380 * @class RawCell
376 * @constructor RawCell
381 * @constructor RawCell
377 * @extends IPython.TextCell
382 * @extends IPython.TextCell
378 */
383 */
379 var RawCell = function (options) {
384 var RawCell = function (options) {
380
385
381 options = this.mergeopt(RawCell,options)
386 options = this.mergeopt(RawCell,options)
382 TextCell.apply(this, [options]);
387 TextCell.apply(this, [options]);
383 this.cell_type = 'raw';
388 this.cell_type = 'raw';
384 // RawCell should always hide its rendered div
389 // RawCell should always hide its rendered div
385 this.element.find('div.text_cell_render').hide();
390 this.element.find('div.text_cell_render').hide();
386 };
391 };
387
392
388 RawCell.options_default = {
393 RawCell.options_default = {
389 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
394 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
390 "It will not be rendered in the notebook.\n" +
395 "It will not be rendered in the notebook.\n" +
391 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
396 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
392 };
397 };
393
398
394 RawCell.prototype = new TextCell();
399 RawCell.prototype = new TextCell();
395
400
396 /** @method bind_events **/
401 /** @method bind_events **/
397 RawCell.prototype.bind_events = function () {
402 RawCell.prototype.bind_events = function () {
398 TextCell.prototype.bind_events.apply(this);
403 TextCell.prototype.bind_events.apply(this);
399 var that = this
404 var that = this
400 this.element.focusout(function() {
405 this.element.focusout(function() {
401 that.auto_highlight();
406 that.auto_highlight();
402 });
407 });
403 };
408 };
404
409
405 /**
410 /**
406 * Trigger autodetection of highlight scheme for current cell
411 * Trigger autodetection of highlight scheme for current cell
407 * @method auto_highlight
412 * @method auto_highlight
408 */
413 */
409 RawCell.prototype.auto_highlight = function () {
414 RawCell.prototype.auto_highlight = function () {
410 this._auto_highlight(IPython.config.raw_cell_highlight);
415 this._auto_highlight(IPython.config.raw_cell_highlight);
411 };
416 };
412
417
413 /** @method render **/
418 /** @method render **/
414 RawCell.prototype.render = function () {
419 RawCell.prototype.render = function () {
415 // Make sure that this cell type can never be rendered
420 // Make sure that this cell type can never be rendered
416 if (this.rendered) {
421 if (this.rendered) {
417 this.unrender();
422 this.unrender();
418 }
423 }
419 var text = this.get_text();
424 var text = this.get_text();
420 if (text === "") { text = this.placeholder; }
425 if (text === "") { text = this.placeholder; }
421 this.set_text(text);
426 this.set_text(text);
422 };
427 };
423
428
424
429
425 /**
430 /**
426 * @class HeadingCell
431 * @class HeadingCell
427 * @extends IPython.TextCell
432 * @extends IPython.TextCell
428 */
433 */
429
434
430 /**
435 /**
431 * @constructor HeadingCell
436 * @constructor HeadingCell
432 * @extends IPython.TextCell
437 * @extends IPython.TextCell
433 */
438 */
434 var HeadingCell = function (options) {
439 var HeadingCell = function (options) {
435 options = this.mergeopt(HeadingCell, options);
440 options = this.mergeopt(HeadingCell, options);
436
441
437 this.level = 1;
442 this.level = 1;
438 this.cell_type = 'heading';
443 this.cell_type = 'heading';
439 TextCell.apply(this, [options]);
444 TextCell.apply(this, [options]);
440
445
441 /**
446 /**
442 * heading level of the cell, use getter and setter to access
447 * heading level of the cell, use getter and setter to access
443 * @property level
448 * @property level
444 */
449 */
445 };
450 };
446
451
447 HeadingCell.options_default = {
452 HeadingCell.options_default = {
448 placeholder: "Type Heading Here"
453 placeholder: "Type Heading Here"
449 };
454 };
450
455
451 HeadingCell.prototype = new TextCell();
456 HeadingCell.prototype = new TextCell();
452
457
453 /** @method fromJSON */
458 /** @method fromJSON */
454 HeadingCell.prototype.fromJSON = function (data) {
459 HeadingCell.prototype.fromJSON = function (data) {
455 if (data.level != undefined){
460 if (data.level != undefined){
456 this.level = data.level;
461 this.level = data.level;
457 }
462 }
458 TextCell.prototype.fromJSON.apply(this, arguments);
463 TextCell.prototype.fromJSON.apply(this, arguments);
459 };
464 };
460
465
461
466
462 /** @method toJSON */
467 /** @method toJSON */
463 HeadingCell.prototype.toJSON = function () {
468 HeadingCell.prototype.toJSON = function () {
464 var data = TextCell.prototype.toJSON.apply(this);
469 var data = TextCell.prototype.toJSON.apply(this);
465 data.level = this.get_level();
470 data.level = this.get_level();
466 return data;
471 return data;
467 };
472 };
468
473
469 /**
474 /**
470 * can the cell be split into two cells
475 * can the cell be split into two cells
471 * @method is_splittable
476 * @method is_splittable
472 **/
477 **/
473 HeadingCell.prototype.is_splittable = function () {
478 HeadingCell.prototype.is_splittable = function () {
474 return false;
479 return false;
475 };
480 };
476
481
477
482
478 /**
483 /**
479 * can the cell be merged with other cells
484 * can the cell be merged with other cells
480 * @method is_mergeable
485 * @method is_mergeable
481 **/
486 **/
482 HeadingCell.prototype.is_mergeable = function () {
487 HeadingCell.prototype.is_mergeable = function () {
483 return false;
488 return false;
484 };
489 };
485
490
486 /**
491 /**
487 * Change heading level of cell, and re-render
492 * Change heading level of cell, and re-render
488 * @method set_level
493 * @method set_level
489 */
494 */
490 HeadingCell.prototype.set_level = function (level) {
495 HeadingCell.prototype.set_level = function (level) {
491 this.level = level;
496 this.level = level;
492 if (this.rendered) {
497 if (this.rendered) {
493 this.rendered = false;
498 this.rendered = false;
494 this.render();
499 this.render();
495 };
500 };
496 };
501 };
497
502
498 /** The depth of header cell, based on html (h1 to h6)
503 /** The depth of header cell, based on html (h1 to h6)
499 * @method get_level
504 * @method get_level
500 * @return {integer} level - for 1 to 6
505 * @return {integer} level - for 1 to 6
501 */
506 */
502 HeadingCell.prototype.get_level = function () {
507 HeadingCell.prototype.get_level = function () {
503 return this.level;
508 return this.level;
504 };
509 };
505
510
506
511
507 HeadingCell.prototype.set_rendered = function (html) {
508 this.element.find("div.text_cell_render").html(html);
509 };
510
511
512 HeadingCell.prototype.get_rendered = function () {
512 HeadingCell.prototype.get_rendered = function () {
513 var r = this.element.find("div.text_cell_render");
513 var r = this.element.find("div.text_cell_render");
514 return r.children().first().html();
514 return r.children().first().html();
515 };
515 };
516
516
517
517
518 HeadingCell.prototype.render = function () {
518 HeadingCell.prototype.render = function () {
519 var cont = IPython.TextCell.prototype.render.apply(this);
519 var cont = IPython.TextCell.prototype.render.apply(this);
520 if (cont) {
520 if (cont) {
521 var text = this.get_text();
521 var text = this.get_text();
522 var math = null;
522 var math = null;
523 // Markdown headings must be a single line
523 // Markdown headings must be a single line
524 text = text.replace(/\n/g, ' ');
524 text = text.replace(/\n/g, ' ');
525 if (text === "") { text = this.placeholder; }
525 if (text === "") { text = this.placeholder; }
526 text = Array(this.level + 1).join("#") + " " + text;
526 text = Array(this.level + 1).join("#") + " " + text;
527 var text_and_math = IPython.mathjaxutils.remove_math(text);
527 var text_and_math = IPython.mathjaxutils.remove_math(text);
528 text = text_and_math[0];
528 text = text_and_math[0];
529 math = text_and_math[1];
529 math = text_and_math[1];
530 var html = marked.parser(marked.lexer(text));
530 var html = marked.parser(marked.lexer(text));
531 var h = $(IPython.mathjaxutils.replace_math(html, math));
531 var h = $(IPython.mathjaxutils.replace_math(html, math));
532 // add id and linkback anchor
532 // add id and linkback anchor
533 var hash = h.text().replace(/ /g, '-');
533 var hash = h.text().replace(/ /g, '-');
534 h.attr('id', hash);
534 h.attr('id', hash);
535 h.append(
535 h.append(
536 $('<a/>')
536 $('<a/>')
537 .addClass('anchor-link')
537 .addClass('anchor-link')
538 .attr('href', '#' + hash)
538 .attr('href', '#' + hash)
539 .text('ΒΆ')
539 .text('ΒΆ')
540 );
540 );
541
541 // TODO: This HTML needs to be treated as potentially dangerous
542 this.set_rendered(h);
542 // user input.
543 var rendered = this.element.find("div.text_cell_render");
544 rendered.html(h);
543 this.typeset();
545 this.typeset();
544 this.element.find('div.text_cell_input').hide();
546 this.element.find('div.text_cell_input').hide();
545 this.element.find("div.text_cell_render").show();
547 rendered.show();
546
548
547 };
549 };
548 return cont;
550 return cont;
549 };
551 };
550
552
551 IPython.TextCell = TextCell;
553 IPython.TextCell = TextCell;
552 IPython.MarkdownCell = MarkdownCell;
554 IPython.MarkdownCell = MarkdownCell;
553 IPython.RawCell = RawCell;
555 IPython.RawCell = RawCell;
554 IPython.HeadingCell = HeadingCell;
556 IPython.HeadingCell = HeadingCell;
555
557
556
558
557 return IPython;
559 return IPython;
558
560
559 }(IPython));
561 }(IPython));
560
562
@@ -1,387 +1,388 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 // Tooltip
8 // Tooltip
9 //============================================================================
9 //============================================================================
10 //
10 //
11 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
11 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
12 //
12 //
13 // you can configure the differents action of pressing tab several times in a row by
13 // you can configure the differents action of pressing tab several times in a row by
14 // setting/appending different fonction in the array
14 // setting/appending different fonction in the array
15 // IPython.tooltip.tabs_functions
15 // IPython.tooltip.tabs_functions
16 //
16 //
17 // eg :
17 // eg :
18 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
18 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
19 //
19 //
20 var IPython = (function (IPython) {
20 var IPython = (function (IPython) {
21 "use strict";
21 "use strict";
22
22
23 var utils = IPython.utils;
23 var utils = IPython.utils;
24
24
25 // tooltip constructor
25 // tooltip constructor
26 var Tooltip = function () {
26 var Tooltip = function () {
27 var that = this;
27 var that = this;
28 this.time_before_tooltip = 1200;
28 this.time_before_tooltip = 1200;
29
29
30 // handle to html
30 // handle to html
31 this.tooltip = $('#tooltip');
31 this.tooltip = $('#tooltip');
32 this._hidden = true;
32 this._hidden = true;
33
33
34 // variable for consecutive call
34 // variable for consecutive call
35 this._old_cell = null;
35 this._old_cell = null;
36 this._old_request = null;
36 this._old_request = null;
37 this._consecutive_counter = 0;
37 this._consecutive_counter = 0;
38
38
39 // 'sticky ?'
39 // 'sticky ?'
40 this._sticky = false;
40 this._sticky = false;
41
41
42 // display tooltip if the docstring is empty?
42 // display tooltip if the docstring is empty?
43 this._hide_if_no_docstring = false;
43 this._hide_if_no_docstring = false;
44
44
45 // contain the button in the upper right corner
45 // contain the button in the upper right corner
46 this.buttons = $('<div/>').addClass('tooltipbuttons');
46 this.buttons = $('<div/>').addClass('tooltipbuttons');
47
47
48 // will contain the docstring
48 // will contain the docstring
49 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
49 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
50
50
51 // build the buttons menu on the upper right
51 // build the buttons menu on the upper right
52 // expand the tooltip to see more
52 // expand the tooltip to see more
53 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
53 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
54 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press tab 2 times)').click(function () {
54 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press tab 2 times)').click(function () {
55 that.expand()
55 that.expand()
56 }).append(
56 }).append(
57 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
57 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
58
58
59 // open in pager
59 // open in pager
60 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)');
60 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)');
61 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
61 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
62 morelink.append(morespan);
62 morelink.append(morespan);
63 morelink.click(function () {
63 morelink.click(function () {
64 that.showInPager(that._old_cell);
64 that.showInPager(that._old_cell);
65 });
65 });
66
66
67 // close the tooltip
67 // close the tooltip
68 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
68 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
69 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
69 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
70 closelink.append(closespan);
70 closelink.append(closespan);
71 closelink.click(function () {
71 closelink.click(function () {
72 that.remove_and_cancel_tooltip(true);
72 that.remove_and_cancel_tooltip(true);
73 });
73 });
74
74
75 this._clocklink = $('<a/>').attr('href', "#");
75 this._clocklink = $('<a/>').attr('href', "#");
76 this._clocklink.attr('role', "button");
76 this._clocklink.attr('role', "button");
77 this._clocklink.addClass('ui-button');
77 this._clocklink.addClass('ui-button');
78 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
78 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
79 var clockspan = $('<span/>').text('Close');
79 var clockspan = $('<span/>').text('Close');
80 clockspan.addClass('ui-icon');
80 clockspan.addClass('ui-icon');
81 clockspan.addClass('ui-icon-clock');
81 clockspan.addClass('ui-icon-clock');
82 this._clocklink.append(clockspan);
82 this._clocklink.append(clockspan);
83 this._clocklink.click(function () {
83 this._clocklink.click(function () {
84 that.cancel_stick();
84 that.cancel_stick();
85 });
85 });
86
86
87
87
88
88
89
89
90 //construct the tooltip
90 //construct the tooltip
91 // add in the reverse order you want them to appear
91 // add in the reverse order you want them to appear
92 this.buttons.append(closelink);
92 this.buttons.append(closelink);
93 this.buttons.append(expandlink);
93 this.buttons.append(expandlink);
94 this.buttons.append(morelink);
94 this.buttons.append(morelink);
95 this.buttons.append(this._clocklink);
95 this.buttons.append(this._clocklink);
96 this._clocklink.hide();
96 this._clocklink.hide();
97
97
98
98
99 // we need a phony element to make the small arrow
99 // we need a phony element to make the small arrow
100 // of the tooltip in css
100 // of the tooltip in css
101 // we will move the arrow later
101 // we will move the arrow later
102 this.arrow = $('<div/>').addClass('pretooltiparrow');
102 this.arrow = $('<div/>').addClass('pretooltiparrow');
103 this.tooltip.append(this.buttons);
103 this.tooltip.append(this.buttons);
104 this.tooltip.append(this.arrow);
104 this.tooltip.append(this.arrow);
105 this.tooltip.append(this.text);
105 this.tooltip.append(this.text);
106
106
107 // function that will be called if you press tab 1, 2, 3... times in a row
107 // function that will be called if you press tab 1, 2, 3... times in a row
108 this.tabs_functions = [function (cell, text) {
108 this.tabs_functions = [function (cell, text) {
109 that._request_tooltip(cell, text);
109 that._request_tooltip(cell, text);
110 }, function () {
110 }, function () {
111 that.expand();
111 that.expand();
112 }, function () {
112 }, function () {
113 that.stick();
113 that.stick();
114 }, function (cell) {
114 }, function (cell) {
115 that.cancel_stick();
115 that.cancel_stick();
116 that.showInPager(cell);
116 that.showInPager(cell);
117 }];
117 }];
118 // call after all the tabs function above have bee call to clean their effects
118 // call after all the tabs function above have bee call to clean their effects
119 // if necessary
119 // if necessary
120 this.reset_tabs_function = function (cell, text) {
120 this.reset_tabs_function = function (cell, text) {
121 this._old_cell = (cell) ? cell : null;
121 this._old_cell = (cell) ? cell : null;
122 this._old_request = (text) ? text : null;
122 this._old_request = (text) ? text : null;
123 this._consecutive_counter = 0;
123 this._consecutive_counter = 0;
124 }
124 }
125 };
125 };
126
126
127 Tooltip.prototype.showInPager = function (cell) {
127 Tooltip.prototype.showInPager = function (cell) {
128 // reexecute last call in pager by appending ? to show back in pager
128 // reexecute last call in pager by appending ? to show back in pager
129 var that = this;
129 var that = this;
130 var empty = function () {};
130 var empty = function () {};
131 cell.kernel.execute(
131 cell.kernel.execute(
132 that.name + '?', {
132 that.name + '?', {
133 'execute_reply': empty,
133 'execute_reply': empty,
134 'output': empty,
134 'output': empty,
135 'clear_output': empty,
135 'clear_output': empty,
136 'cell': cell
136 'cell': cell
137 }, {
137 }, {
138 'silent': false,
138 'silent': false,
139 'store_history': true
139 'store_history': true
140 });
140 });
141 this.remove_and_cancel_tooltip();
141 this.remove_and_cancel_tooltip();
142 }
142 }
143
143
144 // grow the tooltip verticaly
144 // grow the tooltip verticaly
145 Tooltip.prototype.expand = function () {
145 Tooltip.prototype.expand = function () {
146 this.text.removeClass('smalltooltip');
146 this.text.removeClass('smalltooltip');
147 this.text.addClass('bigtooltip');
147 this.text.addClass('bigtooltip');
148 $('#expanbutton').hide('slow');
148 $('#expanbutton').hide('slow');
149 }
149 }
150
150
151 // deal with all the logic of hiding the tooltip
151 // deal with all the logic of hiding the tooltip
152 // and reset it's status
152 // and reset it's status
153 Tooltip.prototype._hide = function () {
153 Tooltip.prototype._hide = function () {
154 this.tooltip.fadeOut('fast');
154 this.tooltip.fadeOut('fast');
155 $('#expanbutton').show('slow');
155 $('#expanbutton').show('slow');
156 this.text.removeClass('bigtooltip');
156 this.text.removeClass('bigtooltip');
157 this.text.addClass('smalltooltip');
157 this.text.addClass('smalltooltip');
158 // keep scroll top to be sure to always see the first line
158 // keep scroll top to be sure to always see the first line
159 this.text.scrollTop(0);
159 this.text.scrollTop(0);
160 this._hidden = true;
160 this._hidden = true;
161 this.code_mirror = null;
161 this.code_mirror = null;
162 }
162 }
163
163
164 // return true on successfully removing a visible tooltip; otherwise return
164 // return true on successfully removing a visible tooltip; otherwise return
165 // false.
165 // false.
166 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
166 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
167 // note that we don't handle closing directly inside the calltip
167 // note that we don't handle closing directly inside the calltip
168 // as in the completer, because it is not focusable, so won't
168 // as in the completer, because it is not focusable, so won't
169 // get the event.
169 // get the event.
170 this.cancel_pending();
170 this.cancel_pending();
171 if (!this._hidden) {
171 if (!this._hidden) {
172 if (force || !this._sticky) {
172 if (force || !this._sticky) {
173 this.cancel_stick();
173 this.cancel_stick();
174 this._hide();
174 this._hide();
175 }
175 }
176 this.reset_tabs_function();
176 this.reset_tabs_function();
177 return true;
177 return true;
178 } else {
178 } else {
179 return false;
179 return false;
180 }
180 }
181 }
181 }
182
182
183 // cancel autocall done after '(' for example.
183 // cancel autocall done after '(' for example.
184 Tooltip.prototype.cancel_pending = function () {
184 Tooltip.prototype.cancel_pending = function () {
185 if (this._tooltip_timeout != null) {
185 if (this._tooltip_timeout != null) {
186 clearTimeout(this._tooltip_timeout);
186 clearTimeout(this._tooltip_timeout);
187 this._tooltip_timeout = null;
187 this._tooltip_timeout = null;
188 }
188 }
189 }
189 }
190
190
191 // will trigger tooltip after timeout
191 // will trigger tooltip after timeout
192 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
192 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
193 var that = this;
193 var that = this;
194 this._tooltip_timeout = setTimeout(function () {
194 this._tooltip_timeout = setTimeout(function () {
195 that.request(cell, hide_if_no_docstring)
195 that.request(cell, hide_if_no_docstring)
196 }, that.time_before_tooltip);
196 }, that.time_before_tooltip);
197 }
197 }
198
198
199 // easy access for julia monkey patching.
199 // easy access for julia monkey patching.
200 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
200 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
201
201
202 Tooltip.prototype.extract_oir_token = function(line){
202 Tooltip.prototype.extract_oir_token = function(line){
203 // use internally just to make the request to the kernel
203 // use internally just to make the request to the kernel
204 // Feel free to shorten this logic if you are better
204 // Feel free to shorten this logic if you are better
205 // than me in regEx
205 // than me in regEx
206 // basicaly you shoul be able to get xxx.xxx.xxx from
206 // basicaly you shoul be able to get xxx.xxx.xxx from
207 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
207 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
208 // remove everything between matchin bracket (need to iterate)
208 // remove everything between matchin bracket (need to iterate)
209 var matchBracket = /\([^\(\)]+\)/g;
209 var matchBracket = /\([^\(\)]+\)/g;
210 var endBracket = /\([^\(]*$/g;
210 var endBracket = /\([^\(]*$/g;
211 var oldline = line;
211 var oldline = line;
212
212
213 line = line.replace(matchBracket, "");
213 line = line.replace(matchBracket, "");
214 while (oldline != line) {
214 while (oldline != line) {
215 oldline = line;
215 oldline = line;
216 line = line.replace(matchBracket, "");
216 line = line.replace(matchBracket, "");
217 }
217 }
218 // remove everything after last open bracket
218 // remove everything after last open bracket
219 line = line.replace(endBracket, "");
219 line = line.replace(endBracket, "");
220 // reset the regex object
220 // reset the regex object
221 Tooltip.last_token_re.lastIndex = 0;
221 Tooltip.last_token_re.lastIndex = 0;
222 return Tooltip.last_token_re.exec(line)
222 return Tooltip.last_token_re.exec(line)
223 };
223 };
224
224
225 Tooltip.prototype._request_tooltip = function (cell, line) {
225 Tooltip.prototype._request_tooltip = function (cell, line) {
226 var callbacks = $.proxy(this._show, this);
226 var callbacks = $.proxy(this._show, this);
227 var oir_token = this.extract_oir_token(line);
227 var oir_token = this.extract_oir_token(line);
228 var msg_id = cell.kernel.object_info(oir_token, callbacks);
228 var msg_id = cell.kernel.object_info(oir_token, callbacks);
229 };
229 };
230
230
231 // make an imediate completion request
231 // make an imediate completion request
232 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
232 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
233 // request(codecell)
233 // request(codecell)
234 // Deal with extracting the text from the cell and counting
234 // Deal with extracting the text from the cell and counting
235 // call in a row
235 // call in a row
236 this.cancel_pending();
236 this.cancel_pending();
237 var editor = cell.code_mirror;
237 var editor = cell.code_mirror;
238 var cursor = editor.getCursor();
238 var cursor = editor.getCursor();
239 var text = editor.getRange({
239 var text = editor.getRange({
240 line: cursor.line,
240 line: cursor.line,
241 ch: 0
241 ch: 0
242 }, cursor).trim();
242 }, cursor).trim();
243
243
244 this._hide_if_no_docstring = hide_if_no_docstring;
244 this._hide_if_no_docstring = hide_if_no_docstring;
245
245
246 if(editor.somethingSelected()){
246 if(editor.somethingSelected()){
247 text = editor.getSelection();
247 text = editor.getSelection();
248 }
248 }
249
249
250 // need a permanent handel to code_mirror for future auto recall
250 // need a permanent handel to code_mirror for future auto recall
251 this.code_mirror = editor;
251 this.code_mirror = editor;
252
252
253 // now we treat the different number of keypress
253 // now we treat the different number of keypress
254 // first if same cell, same text, increment counter by 1
254 // first if same cell, same text, increment counter by 1
255 if (this._old_cell == cell && this._old_request == text && this._hidden == false) {
255 if (this._old_cell == cell && this._old_request == text && this._hidden == false) {
256 this._consecutive_counter++;
256 this._consecutive_counter++;
257 } else {
257 } else {
258 // else reset
258 // else reset
259 this.cancel_stick();
259 this.cancel_stick();
260 this.reset_tabs_function (cell, text);
260 this.reset_tabs_function (cell, text);
261 }
261 }
262
262
263 // don't do anything if line beggin with '(' or is empty
263 // don't do anything if line beggin with '(' or is empty
264 if (text === "" || text === "(") {
264 if (text === "" || text === "(") {
265 return;
265 return;
266 }
266 }
267
267
268 this.tabs_functions[this._consecutive_counter](cell, text);
268 this.tabs_functions[this._consecutive_counter](cell, text);
269
269
270 // then if we are at the end of list function, reset
270 // then if we are at the end of list function, reset
271 if (this._consecutive_counter == this.tabs_functions.length) {
271 if (this._consecutive_counter == this.tabs_functions.length) {
272 this.reset_tabs_function (cell, text);
272 this.reset_tabs_function (cell, text);
273 }
273 }
274
274
275 return;
275 return;
276 }
276 }
277
277
278 // cancel the option of having the tooltip to stick
278 // cancel the option of having the tooltip to stick
279 Tooltip.prototype.cancel_stick = function () {
279 Tooltip.prototype.cancel_stick = function () {
280 clearTimeout(this._stick_timeout);
280 clearTimeout(this._stick_timeout);
281 this._stick_timeout = null;
281 this._stick_timeout = null;
282 this._clocklink.hide('slow');
282 this._clocklink.hide('slow');
283 this._sticky = false;
283 this._sticky = false;
284 }
284 }
285
285
286 // put the tooltip in a sicky state for 10 seconds
286 // put the tooltip in a sicky state for 10 seconds
287 // it won't be removed by remove_and_cancell() unless you called with
287 // it won't be removed by remove_and_cancell() unless you called with
288 // the first parameter set to true.
288 // the first parameter set to true.
289 // remove_and_cancell_tooltip(true)
289 // remove_and_cancell_tooltip(true)
290 Tooltip.prototype.stick = function (time) {
290 Tooltip.prototype.stick = function (time) {
291 time = (time != undefined) ? time : 10;
291 time = (time != undefined) ? time : 10;
292 var that = this;
292 var that = this;
293 this._sticky = true;
293 this._sticky = true;
294 this._clocklink.show('slow');
294 this._clocklink.show('slow');
295 this._stick_timeout = setTimeout(function () {
295 this._stick_timeout = setTimeout(function () {
296 that._sticky = false;
296 that._sticky = false;
297 that._clocklink.hide('slow');
297 that._clocklink.hide('slow');
298 }, time * 1000);
298 }, time * 1000);
299 }
299 }
300
300
301 // should be called with the kernel reply to actually show the tooltip
301 // should be called with the kernel reply to actually show the tooltip
302 Tooltip.prototype._show = function (reply) {
302 Tooltip.prototype._show = function (reply) {
303 // move the bubble if it is not hidden
303 // move the bubble if it is not hidden
304 // otherwise fade it
304 // otherwise fade it
305 var content = reply.content;
305 var content = reply.content;
306 if (!content.found) {
306 if (!content.found) {
307 // object not found, nothing to show
307 // object not found, nothing to show
308 return;
308 return;
309 }
309 }
310 this.name = content.name;
310 this.name = content.name;
311
311
312 // do some math to have the tooltip arrow on more or less on left or right
312 // do some math to have the tooltip arrow on more or less on left or right
313 // width of the editor
313 // width of the editor
314 var w = $(this.code_mirror.getScrollerElement()).width();
314 var w = $(this.code_mirror.getScrollerElement()).width();
315 // ofset of the editor
315 // ofset of the editor
316 var o = $(this.code_mirror.getScrollerElement()).offset();
316 var o = $(this.code_mirror.getScrollerElement()).offset();
317
317
318 // whatever anchor/head order but arrow at mid x selection
318 // whatever anchor/head order but arrow at mid x selection
319 var anchor = this.code_mirror.cursorCoords(false);
319 var anchor = this.code_mirror.cursorCoords(false);
320 var head = this.code_mirror.cursorCoords(true);
320 var head = this.code_mirror.cursorCoords(true);
321 var xinit = (head.left+anchor.left)/2;
321 var xinit = (head.left+anchor.left)/2;
322 var xinter = o.left + (xinit - o.left) / w * (w - 450);
322 var xinter = o.left + (xinit - o.left) / w * (w - 450);
323 var posarrowleft = xinit - xinter;
323 var posarrowleft = xinit - xinter;
324
324
325 if (this._hidden == false) {
325 if (this._hidden == false) {
326 this.tooltip.animate({
326 this.tooltip.animate({
327 'left': xinter - 30 + 'px',
327 'left': xinter - 30 + 'px',
328 'top': (head.bottom + 10) + 'px'
328 'top': (head.bottom + 10) + 'px'
329 });
329 });
330 } else {
330 } else {
331 this.tooltip.css({
331 this.tooltip.css({
332 'left': xinter - 30 + 'px'
332 'left': xinter - 30 + 'px'
333 });
333 });
334 this.tooltip.css({
334 this.tooltip.css({
335 'top': (head.bottom + 10) + 'px'
335 'top': (head.bottom + 10) + 'px'
336 });
336 });
337 }
337 }
338 this.arrow.animate({
338 this.arrow.animate({
339 'left': posarrowleft + 'px'
339 'left': posarrowleft + 'px'
340 });
340 });
341
341
342 // build docstring
342 // build docstring
343 var defstring = content.call_def;
343 var defstring = content.call_def;
344 if (!defstring) {
344 if (!defstring) {
345 defstring = content.init_definition;
345 defstring = content.init_definition;
346 }
346 }
347 if (!defstring) {
347 if (!defstring) {
348 defstring = content.definition;
348 defstring = content.definition;
349 }
349 }
350
350
351 var docstring = content.call_docstring;
351 var docstring = content.call_docstring;
352 if (!docstring) {
352 if (!docstring) {
353 docstring = content.init_docstring;
353 docstring = content.init_docstring;
354 }
354 }
355 if (!docstring) {
355 if (!docstring) {
356 docstring = content.docstring;
356 docstring = content.docstring;
357 }
357 }
358
358
359 if (!docstring) {
359 if (!docstring) {
360 // For reals this time, no docstring
360 // For reals this time, no docstring
361 if (this._hide_if_no_docstring) {
361 if (this._hide_if_no_docstring) {
362 return;
362 return;
363 } else {
363 } else {
364 docstring = "<empty docstring>";
364 docstring = "<empty docstring>";
365 }
365 }
366 }
366 }
367
367
368 this.tooltip.fadeIn('fast');
368 this.tooltip.fadeIn('fast');
369 this._hidden = false;
369 this._hidden = false;
370 this.text.children().remove();
370 this.text.children().remove();
371
371
372 // Any HTML within the docstring is escaped by the fixConsole() method.
372 var pre = $('<pre/>').html(utils.fixConsole(docstring));
373 var pre = $('<pre/>').html(utils.fixConsole(docstring));
373 if (defstring) {
374 if (defstring) {
374 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
375 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
375 this.text.append(defstring_html);
376 this.text.append(defstring_html);
376 }
377 }
377 this.text.append(pre);
378 this.text.append(pre);
378 // keep scroll top to be sure to always see the first line
379 // keep scroll top to be sure to always see the first line
379 this.text.scrollTop(0);
380 this.text.scrollTop(0);
380 }
381 }
381
382
382
383
383 IPython.Tooltip = Tooltip;
384 IPython.Tooltip = Tooltip;
384
385
385 return IPython;
386 return IPython;
386
387
387 }(IPython));
388 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now