Merge pull request #1711 from ipython/tooltipCompleterJSRefactor

New Tooltip, New Completer and JS Refactor.

This is a major reworking of lots of notebook client code, both introducing new features and allowing certain things to be done more cleanly:

- New graphical tooltip with keyboard control: successive presses of the TAB key will expand it, pin it for 10 s (a clock icon is shown), and send its content to the bottom help pager (which is now resizable).

- Completer has been refactored into a new class and now can analyze the current cell to support completions on objects that don't exist in the kernel yet (because the cell hasn't been executed).

- All the client-side JavaScript has been decoupled to ease reuse of parts of the machinery without having to build a full-blown notebook. This will make it much easier to communicate with an IPython kernel from existing web pages and to integrate single cells into other sites, without loading the full notebook document-like UI.

- This refactoring also enables the possibility of writing dynamic javascript widgets that are returned from Python code and that present an interactive view to the user, with callbacks in Javascript executing calls to the Kernel.  This will enable many interactive elements to be added by users in notebooks.  

An example of this capability has been provided as a proof of concept in `docs/examples/widgets` that lets you directly communicate with one or more parallel engines, acting as a mini-console for parallel debugging and introspection.

Closes #1498.
pull/37/head
Fernando Perez 14 years ago
commit ac548bd8bc

@ -172,7 +172,7 @@ div.output_area {
/* This class is for the output subarea inside the output_area and after
the prompt div. */
div.output_subarea {
padding: 0.4em 6.1em 0.4em 0.4em;
padding: 0.4em 0.4em 0.4em 0.4em;
}
/* The rest of the output_* classes are for special styling of the different
@ -257,11 +257,11 @@ div.text_cell_render {
.ansigrey {color: grey;}
.ansibold {font-weight: bold;}
.completions , .tooltip {
.completions {
position: absolute;
z-index: 10;
overflow: auto;
border: 1px solid black;
border: 1px solid grey;
}
.completions select {
@ -273,61 +273,11 @@ div.text_cell_render {
font-family: monospace;
}
@-moz-keyframes fadeIn {
from {opacity:0;}
to {opacity:1;}
option.context {
background-color: #DEF7FF;
}
@-webkit-keyframes fadeIn {
from {opacity:0;}
to {opacity:1;}
}
@keyframes fadeIn {
from {opacity:0;}
to {opacity:1;}
}
/*"close" "expand" and "Open in pager button" of
/* the tooltip*/
.tooltip a {
float:right;
}
/*properties of tooltip after "expand"*/
.bigtooltip {
height:30%;
}
/*properties of tooltip before "expand"*/
.smalltooltip {
text-overflow: ellipsis;
overflow: hidden;
height:15%;
}
.tooltip {
/*transition when "expand"ing tooltip */
-webkit-transition-property: height;
-webkit-transition-duration: 1s;
-moz-transition-property: height;
-moz-transition-duration: 1s;
transition-property: height;
transition-duration: 1s;
max-width:700px;
border-radius: 0px 10px 10px 10px;
box-shadow: 3px 3px 5px #999;
/*fade-in animation when inserted*/
-webkit-animation: fadeIn 200ms;
-moz-animation: fadeIn 200ms;
animation: fadeIn 200ms;
vertical-align: middle;
background: #FDFDD8;
outline: none;
padding: 3px;
margin: 0px;
font-family: monospace;
min-height:50px;
option.introspection {
background-color: #EBF4EB;
}
/*fixed part of the completion*/

@ -0,0 +1,133 @@
/**
* Primary styles
*
* Author: IPython Development Team
*/
/** WARNING IF YOU ARE EDITTING THIS FILE, if this is a .css file, It has a lot
* of chance of beeing generated from the ../less/[samename].less file, you can
* try to get back the less file by reverting somme commit in history
**/
/*
* We'll try to get something pretty, so we
* have some strange css to have the scroll bar on
* the left with fix button on the top right of the tooltip
*/
@-moz-keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@-webkit-keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@-moz-keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@-webkit-keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.tooltip a {
float: right;
}
/*properties of tooltip after "expand"*/
.bigtooltip {
overflow: auto;
height: 200px;
-webkit-transition-property: height;
-webkit-transition-duration: 1s;
-moz-transition-property: height;
-moz-transition-duration: 1s;
transition-property: height;
transition-duration: 1s;
}
/*properties of tooltip before "expand"*/
.smalltooltip {
-webkit-transition-property: height;
-webkit-transition-duration: 1s;
-moz-transition-property: height;
-moz-transition-duration: 1s;
transition-property: height;
transition-duration: 1s;
text-overflow: ellipsis;
overflow: hidden;
height: 80px;
}
.tooltipbuttons {
position: absolute;
padding-right: 15px;
top: 0px;
right: 0px;
}
.tooltiptext {
/*avoid the button to overlap on some docstring*/
padding-right: 30px;
}
.tooltip {
max-width: 700px;
border-radius: 4px;
-moz-box-shadow: 0px 6px 10px -1px #adadad;
-webkit-box-shadow: 0px 6px 10px -1px #adadad;
box-shadow: 0px 6px 10px -1px #adadad;
/*fade-in animation when inserted*/
-webkit-animation: fadeOut 800ms;
-moz-animation: fadeOut 800ms;
animation: fadeOut 800ms;
-webkit-animation: fadeIn 800ms;
-moz-animation: fadeIn 800ms;
animation: fadeIn 800ms;
vertical-align: middle;
background-color: #f7f7f7;
overflow: visible;
border: #bbbbbb 1px solid;
outline: none;
padding: 3px;
margin: 0px;
padding-left: 7px;
font-family: monospace;
min-height: 50px;
position: absolute;
}
.pretooltiparrow {
left: 0px;
margin: 0px;
top: -16px;
width: 40px;
height: 16px;
overflow: hidden;
position: absolute;
}
.pretooltiparrow:before {
background-color: #f7f7f7;
border: 1px #bbbbbb solid;
z-index: 11;
content: "";
position: absolute;
left: 15px;
top: 10px;
width: 25px;
height: 25px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
}

@ -14,13 +14,9 @@ var IPython = (function (IPython) {
var utils = IPython.utils;
var Cell = function (notebook) {
var Cell = function () {
this.placeholder = this.placeholder || '';
this.notebook = notebook;
this.read_only = false;
if (notebook){
this.read_only = notebook.read_only;
}
this.selected = false;
this.element = null;
this.create_element();
@ -38,15 +34,15 @@ var IPython = (function (IPython) {
Cell.prototype.bind_events = function () {
var that = this;
var nb = that.notebook;
// We trigger events so that Cell doesn't have to depend on Notebook.
that.element.click(function (event) {
if (that.selected === false) {
nb.select(nb.find_cell_index(that));
$([IPython.events]).trigger('select.Cell', {'cell':that});
}
});
that.element.focusin(function (event) {
if (that.selected === false) {
nb.select(nb.find_cell_index(that));
$([IPython.events]).trigger('select.Cell', {'cell':that});
}
});
};

@ -10,18 +10,18 @@
//============================================================================
var IPython = (function (IPython) {
"use strict";
var utils = IPython.utils;
var key = IPython.utils.keycodes;
var CodeCell = function (notebook) {
var CodeCell = function (kernel) {
// The kernel doesn't have to be set at creation time, in that case
// it will be null and set_kernel has to be called later.
this.kernel = kernel || null;
this.code_mirror = null;
this.input_prompt_number = null;
this.is_completing = false;
this.completion_cursor = null;
this.outputs = [];
this.collapsed = false;
this.tooltip_timeout = null;
this.clear_out_timeout = null;
this.tooltip_on_tab = true;
IPython.Cell.apply(this, arguments);
};
@ -43,22 +43,16 @@ var IPython = (function (IPython) {
onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
});
input.append(input_area);
var output = $('<div></div>').addClass('output vbox');
var output = $('<div></div>');
cell.append(input).append(output);
this.element = cell;
this.collapse();
};
this.output_area = new IPython.OutputArea(output, true);
//TODO, try to diminish the number of parameters.
CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time){
var that = this;
if (pre_cursor === "" || pre_cursor === "(" ) {
// don't do anything if line beggin with '(' or is empty
} else {
// Will set a timer to request tooltip in `time`
that.tooltip_timeout = setTimeout(function(){
IPython.notebook.request_tool_tip(that, pre_cursor)
},time);
// construct a completer only if class exist
// otherwise no print view
if (IPython.Completer !== undefined)
{
this.completer = new IPython.Completer(this);
}
};
@ -72,30 +66,24 @@ var IPython = (function (IPython) {
return false;
}
// note that we are comparing and setting the time to wait at each key press.
// a better wqy might be to generate a new function on each time change and
// assign it to CodeCell.prototype.request_tooltip_after_time
tooltip_wait_time = this.notebook.time_before_tooltip;
tooltip_on_tab = this.notebook.tooltip_on_tab;
var that = this;
// whatever key is pressed, first, cancel the tooltip request before
// they are sent, and remove tooltip if any
if(event.type === 'keydown' ) {
that.remove_and_cancel_tooltip();
// they are sent, and remove tooltip if any, except for tab again
if (event.type === 'keydown' && event.which != key.TAB ) {
IPython.tooltip.remove_and_cancel_tooltip();
};
var cur = editor.getCursor();
if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
// Always ignore shift-enter in CodeMirror as we handle it.
return true;
} else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
// triger aon keypress (!) otherwise inconsistent event.which depending on plateform
} else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
// triger on keypress (!) otherwise inconsistent event.which depending on plateform
// browser and keyboard layout !
// Pressing '(' , request tooltip, don't forget to reappend it
var cursor = editor.getCursor();
var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
that.request_tooltip_after_time(pre_cursor,tooltip_wait_time);
} else if (event.which === 38) {
IPython.tooltip.pending(that);
} else if (event.which === key.UPARROW && event.type === 'keydown') {
// If we are not at the top, let CM handle the up arrow and
// prevent the global keydown handler from handling it.
if (!that.at_top()) {
@ -104,7 +92,10 @@ var IPython = (function (IPython) {
} else {
return true;
};
} else if (event.which === 40) {
} else if (event.which === key.ESC) {
IPython.tooltip.remove_and_cancel_tooltip(true);
return true;
} else if (event.which === key.DOWNARROW && event.type === 'keydown') {
// If we are not at the bottom, let CM handle the down arrow and
// prevent the global keydown handler from handling it.
if (!that.at_bottom()) {
@ -113,34 +104,27 @@ var IPython = (function (IPython) {
} else {
return true;
};
} else if (event.keyCode === 9 && event.type == 'keydown') {
} else if (event.keyCode === key.TAB && event.type == 'keydown') {
// Tab completion.
var cur = editor.getCursor();
//Do not trim here because of tooltip
var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
if (pre_cursor.trim() === "") {
// Don't autocomplete if the part of the line before the cursor
// is empty. In this case, let CodeMirror handle indentation.
return false;
} else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
that.request_tooltip_after_time(pre_cursor,0);
} else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && that.tooltip_on_tab ) {
IPython.tooltip.request(that);
// Prevent the event from bubbling up.
event.stop();
// Prevent CodeMirror from handling the tab.
return true;
} else {
pre_cursor.trim();
// Autocomplete the current line.
event.stop();
var line = editor.getLine(cur.line);
this.is_completing = true;
this.completion_cursor = cur;
IPython.notebook.complete_cell(this, line, cur.ch);
this.completer.startCompletion();
return true;
};
} else if (event.keyCode === 8 && event.type == 'keydown') {
} else if (event.keyCode === key.BACKSPACE && event.type == 'keydown') {
// If backspace and the line ends with 4 spaces, remove them.
var cur = editor.getCursor();
var line = editor.getLine(cur.line);
var ending = line.slice(-4);
if (ending === ' ') {
@ -156,394 +140,45 @@ var IPython = (function (IPython) {
} else {
// keypress/keyup also trigger on TAB press, and we don't want to
// use those to disable tab completion.
if (this.is_completing && event.keyCode !== 9) {
var ed_cur = editor.getCursor();
var cc_cur = this.completion_cursor;
if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
this.is_completing = false;
this.completion_cursor = null;
};
};
return false;
};
return false;
};
CodeCell.prototype.remove_and_cancel_tooltip = function() {
// note that we don't handle closing directly inside the calltip
// as in the completer, because it is not focusable, so won't
// get the event.
if (this.tooltip_timeout != null){
clearTimeout(this.tooltip_timeout);
$('#tooltip').remove();
this.tooltip_timeout = null;
}
}
CodeCell.prototype.finish_tooltip = function (reply) {
// Extract call tip data; the priority is call, init, main.
defstring = reply.call_def;
if (defstring == null) { defstring = reply.init_definition; }
if (defstring == null) { defstring = reply.definition; }
docstring = reply.call_docstring;
if (docstring == null) { docstring = reply.init_docstring; }
if (docstring == null) { docstring = reply.docstring; }
if (docstring == null) { docstring = "<empty docstring>"; }
name=reply.name;
var that = this;
var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
// remove to have the tooltip not Limited in X and Y
tooltip.addClass('smalltooltip');
var pre=$('<pre/>').html(utils.fixConsole(docstring));
var expandlink=$('<a/>').attr('href',"#");
expandlink.addClass("ui-corner-all"); //rounded corner
expandlink.attr('role',"button");
//expandlink.addClass('ui-button');
//expandlink.addClass('ui-state-default');
var expandspan=$('<span/>').text('Expand');
expandspan.addClass('ui-icon');
expandspan.addClass('ui-icon-plus');
expandlink.append(expandspan);
expandlink.attr('id','expanbutton');
expandlink.click(function(){
tooltip.removeClass('smalltooltip');
tooltip.addClass('bigtooltip');
$('#expanbutton').remove();
setTimeout(function(){that.code_mirror.focus();}, 50);
});
var morelink=$('<a/>').attr('href',"#");
morelink.attr('role',"button");
morelink.addClass('ui-button');
//morelink.addClass("ui-corner-all"); //rounded corner
//morelink.addClass('ui-state-default');
var morespan=$('<span/>').text('Open in Pager');
morespan.addClass('ui-icon');
morespan.addClass('ui-icon-arrowstop-l-n');
morelink.append(morespan);
morelink.click(function(){
var msg_id = IPython.notebook.kernel.execute(name+"?");
IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.get_selected_cell().cell_id;
that.remove_and_cancel_tooltip();
setTimeout(function(){that.code_mirror.focus();}, 50);
});
var closelink=$('<a/>').attr('href',"#");
closelink.attr('role',"button");
closelink.addClass('ui-button');
//closelink.addClass("ui-corner-all"); //rounded corner
//closelink.adClass('ui-state-default'); // grey background and blue cross
var closespan=$('<span/>').text('Close');
closespan.addClass('ui-icon');
closespan.addClass('ui-icon-close');
closelink.append(closespan);
closelink.click(function(){
that.remove_and_cancel_tooltip();
setTimeout(function(){that.code_mirror.focus();}, 50);
});
//construct the tooltip
tooltip.append(closelink);
tooltip.append(expandlink);
tooltip.append(morelink);
if(defstring){
defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
tooltip.append(defstring_html);
}
tooltip.append(pre);
var pos = this.code_mirror.cursorCoords();
tooltip.css('left',pos.x+'px');
tooltip.css('top',pos.yBot+'px');
$('body').append(tooltip);
// issues with cross-closing if multiple tooltip in less than 5sec
// keep it comented for now
// setTimeout(that.remove_and_cancel_tooltip, 5000);
};
// As you type completer
CodeCell.prototype.finish_completing = function (matched_text, matches) {
if(matched_text[0]=='%'){
completing_from_magic = true;
completing_to_magic = false;
} else {
completing_from_magic = false;
completing_to_magic = false;
}
//return if not completing or nothing to complete
if (!this.is_completing || matches.length === 0) {return;}
// for later readability
var key = { tab:9,
esc:27,
backspace:8,
space:32,
shift:16,
enter:13,
// _ is 95
isCompSymbol : function (code)
{
return (code > 64 && code <= 90)
|| (code >= 97 && code <= 122)
|| (code == 95)
},
dismissAndAppend : function (code)
{
chararr = '()[]+-/\\. ,=*'.split("");
codearr = chararr.map(function(x){return x.charCodeAt(0)});
return jQuery.inArray(code, codearr) != -1;
}
}
// smart completion, sort kwarg ending with '='
var newm = new Array();
if(this.notebook.smart_completer)
{
kwargs = new Array();
other = new Array();
for(var i = 0 ; i<matches.length ; ++i){
if(matches[i].substr(-1) === '='){
kwargs.push(matches[i]);
}else{other.push(matches[i]);}
}
newm = kwargs.concat(other);
matches = newm;
}
// end sort kwargs
// give common prefix of a array of string
function sharedStart(A){
shared='';
if(A.length == 1){shared=A[0]}
if(A.length > 1 ){
var tem1, tem2, s, A = A.slice(0).sort();
tem1 = A[0];
s = tem1.length;
tem2 = A.pop();
while(s && tem2.indexOf(tem1) == -1){
tem1 = tem1.substring(0, --s);
}
shared = tem1;
}
if (shared[0] == '%' && !completing_from_magic)
{
shared = shared.substr(1);
return [shared, true];
} else {
return [shared, false];
}
}
// Kernel related calls.
CodeCell.prototype.set_kernel = function (kernel) {
this.kernel = kernel;
}
//try to check if the user is typing tab at least twice after a word
// and completion is "done"
fallback_on_tooltip_after = 2
if(matches.length == 1 && matched_text === matches[0])
{
if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
{
this.request_tooltip_after_time(matched_text+'(',0);
return;
}
this.prevmatch = matched_text
this.npressed = this.npressed+1;
}
else
{
this.prevmatch = "";
this.npressed = 0;
}
// end fallback on tooltip
//==================================
// Real completion logic start here
var that = this;
var cur = this.completion_cursor;
var done = false;
// call to dismmiss the completer
var close = function () {
if (done) return;
done = true;
if (complete != undefined)
{complete.remove();}
that.is_completing = false;
that.completion_cursor = null;
};
// update codemirror with the typed text
prev = matched_text
var update = function (inserted_text, event) {
that.code_mirror.replaceRange(
inserted_text,
{line: cur.line, ch: (cur.ch-matched_text.length)},
{line: cur.line, ch: (cur.ch+prev.length-matched_text.length)}
);
prev = inserted_text
if(event != null){
event.stopPropagation();
event.preventDefault();
}
};
// insert the given text and exit the completer
var insert = function (selected_text, event) {
update(selected_text)
close();
setTimeout(function(){that.code_mirror.focus();}, 50);
};
// insert the curent highlited selection and exit
var pick = function () {
insert(select.val()[0],null);
CodeCell.prototype.execute = function () {
this.output_area.clear_output(true, true, true);
this.set_input_prompt('*');
this.element.addClass("running");
var callbacks = {
'execute_reply': $.proxy(this._handle_execute_reply, this),
'output': $.proxy(this.output_area.handle_output, this.output_area),
'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
'set_next_input': $.proxy(this._handle_set_next_input, this)
};
var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
};
// Define function to clear the completer, refill it with the new
// matches, update the pseuso typing field. autopick insert match if
// only one left, in no matches (anymore) dismiss itself by pasting
// what the user have typed until then
var complete_with = function(matches,typed_text,autopick,event)
{
// If autopick an only one match, past.
// Used to 'pick' when pressing tab
var prefix = '';
if(completing_to_magic && !completing_from_magic)
{
prefix='%';
}
if (matches.length < 1) {
insert(prefix+typed_text,event);
if(event != null){
event.stopPropagation();
event.preventDefault();
}
} else if (autopick && matches.length == 1) {
insert(matches[0],event);
if(event != null){
event.stopPropagation();
event.preventDefault();
}
return;
}
//clear the previous completion if any
update(prefix+typed_text,event);
complete.children().children().remove();
$('#asyoutype').html("<b>"+prefix+matched_text+"</b>"+typed_text.substr(matched_text.length));
select = $('#asyoutypeselect');
for (var i = 0; i<matches.length; ++i) {
select.append($('<option/>').html(matches[i]));
}
select.children().first().attr('selected','true');
}
CodeCell.prototype._handle_execute_reply = function (content) {
this.set_input_prompt(content.execution_count);
this.element.removeClass("running");
$([IPython.events]).trigger('set_dirty.Notebook', {'value': true});
}
// create html for completer
var complete = $('<div/>').addClass('completions');
complete.attr('id','complete');
complete.append($('<p/>').attr('id', 'asyoutype').html('<b>fixed part</b>user part'));//pseudo input field
var select = $('<select/>').attr('multiple','true');
select.attr('id', 'asyoutypeselect')
select.attr('size',Math.min(10,matches.length));
var pos = this.code_mirror.cursorCoords();
// TODO: I propose to remove enough horizontal pixel
// to align the text later
complete.css('left',pos.x+'px');
complete.css('top',pos.yBot+'px');
complete.append(select);
$('body').append(complete);
// So a first actual completion. see if all the completion start wit
// the same letter and complete if necessary
ff = sharedStart(matches)
fastForward = ff[0];
completing_to_magic = ff[1];
typed_characters = fastForward.substr(matched_text.length);
complete_with(matches,matched_text+typed_characters,true,null);
filterd = matches;
// Give focus to select, and make it filter the match as the user type
// by filtering the previous matches. Called by .keypress and .keydown
var downandpress = function (event,press_or_down) {
var code = event.which;
var autopick = false; // auto 'pick' if only one match
if (press_or_down === 0){
press = true; down = false; //Are we called from keypress or keydown
} else if (press_or_down == 1){
press = false; down = true;
}
if (code === key.shift) {
// nothing on Shift
return;
}
if (key.dismissAndAppend(code) && press) {
var newchar = String.fromCharCode(code);
typed_characters = typed_characters+newchar;
insert(matched_text+typed_characters,event);
return
}
if (code === key.enter) {
// Pressing ENTER will cause a pick
event.stopPropagation();
event.preventDefault();
pick();
} else if (code === 38 || code === 40) {
// We don't want the document keydown handler to handle UP/DOWN,
// but we want the default action.
event.stopPropagation();
} else if ( (code == key.backspace)||(code == key.tab && down) || press || key.isCompSymbol(code)){
if( key.isCompSymbol(code) && press)
{
var newchar = String.fromCharCode(code);
typed_characters = typed_characters+newchar;
} else if (code == key.tab) {
ff = sharedStart(matches)
fastForward = ff[0];
completing_to_magic = ff[1];
ffsub = fastForward.substr(matched_text.length+typed_characters.length);
typed_characters = typed_characters+ffsub;
autopick = true;
} else if (code == key.backspace && down) {
// cancel if user have erase everything, otherwise decrease
// what we filter with
event.preventDefault();
if (typed_characters.length <= 0)
{
insert(matched_text,event)
return
}
typed_characters = typed_characters.substr(0,typed_characters.length-1);
} else if (press && code != key.backspace && code != key.tab && code != 0){
insert(matched_text+typed_characters,event);
return
} else {
return
}
re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
filterd = matches.filter(function(x){return re.test(x)});
ff = sharedStart(filterd);
completing_to_magic = ff[1];
complete_with(filterd,matched_text+typed_characters,autopick,event);
} else if (code == key.esc) {
// dismiss the completer and go back to before invoking it
insert(matched_text,event);
} else if (press) { // abort only on .keypress or esc
}
}
select.keydown(function (event) {
downandpress(event,1)
});
select.keypress(function (event) {
downandpress(event,0)
});
// Double click also causes a pick.
// and bind the last actions.
select.dblclick(pick);
select.blur(close);
select.focus();
};
CodeCell.prototype._handle_set_next_input = function (text) {
var data = {'cell': this, 'text': text}
$([IPython.events]).trigger('set_next_input.Notebook', data);
}
// Basic cell manipulation.
CodeCell.prototype.select = function () {
IPython.Cell.prototype.select.apply(this);
@ -564,281 +199,21 @@ var IPython = (function (IPython) {
};
CodeCell.prototype.append_output = function (json, dynamic) {
// If dynamic is true, javascript output will be eval'd.
this.expand();
this.flush_clear_timeout();
if (json.output_type === 'pyout') {
this.append_pyout(json, dynamic);
} else if (json.output_type === 'pyerr') {
this.append_pyerr(json);
} else if (json.output_type === 'display_data') {
this.append_display_data(json, dynamic);
} else if (json.output_type === 'stream') {
this.append_stream(json);
};
this.outputs.push(json);
};
CodeCell.prototype.create_output_area = function () {
var oa = $("<div/>").addClass("hbox output_area");
oa.append($('<div/>').addClass('prompt'));
return oa;
};
CodeCell.prototype.append_pyout = function (json, dynamic) {
n = json.prompt_number || ' ';
var toinsert = this.create_output_area();
toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
this.append_mime_type(json, toinsert, dynamic);
this.element.find('div.output').append(toinsert);
// If we just output latex, typeset it.
if ((json.latex !== undefined) || (json.html !== undefined)) {
this.typeset();
};
};
CodeCell.prototype.append_pyerr = function (json) {
var tb = json.traceback;
if (tb !== undefined && tb.length > 0) {
var s = '';
var len = tb.length;
for (var i=0; i<len; i++) {
s = s + tb[i] + '\n';
}
s = s + '\n';
var toinsert = this.create_output_area();
this.append_text(s, toinsert);
this.element.find('div.output').append(toinsert);
};
};
CodeCell.prototype.append_stream = function (json) {
// temporary fix: if stream undefined (json file written prior to this patch),
// default to most likely stdout:
if (json.stream == undefined){
json.stream = 'stdout';
}
if (!utils.fixConsole(json.text)){
// fixConsole gives nothing (empty string, \r, etc.)
// so don't append any elements, which might add undesirable space
return;
}
var subclass = "output_"+json.stream;
if (this.outputs.length > 0){
// have at least one output to consider
var last = this.outputs[this.outputs.length-1];
if (last.output_type == 'stream' && json.stream == last.stream){
// latest output was in the same stream,
// so append directly into its pre tag
// escape ANSI & HTML specials:
var text = utils.fixConsole(json.text);
this.element.find('div.'+subclass).last().find('pre').append(text);
return;
}
}
// If we got here, attach a new div
var toinsert = this.create_output_area();
this.append_text(json.text, toinsert, "output_stream "+subclass);
this.element.find('div.output').append(toinsert);
};
CodeCell.prototype.append_display_data = function (json, dynamic) {
var toinsert = this.create_output_area();
this.append_mime_type(json, toinsert, dynamic);
this.element.find('div.output').append(toinsert);
// If we just output latex, typeset it.
if ( (json.latex !== undefined) || (json.html !== undefined) ) {
this.typeset();
};
};
CodeCell.prototype.append_mime_type = function (json, element, dynamic) {
if (json.javascript !== undefined && dynamic) {
this.append_javascript(json.javascript, element, dynamic);
} else if (json.html !== undefined) {
this.append_html(json.html, element);
} else if (json.latex !== undefined) {
this.append_latex(json.latex, element);
} else if (json.svg !== undefined) {
this.append_svg(json.svg, element);
} else if (json.png !== undefined) {
this.append_png(json.png, element);
} else if (json.jpeg !== undefined) {
this.append_jpeg(json.jpeg, element);
} else if (json.text !== undefined) {
this.append_text(json.text, element);
};
};
CodeCell.prototype.append_html = function (html, element) {
var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
toinsert.append(html);
element.append(toinsert);
};
CodeCell.prototype.append_javascript = function (js, container) {
// We just eval the JS code, element appears in the local scope.
var element = $("<div/>").addClass("box_flex1 output_subarea");
container.append(element);
// Div for js shouldn't be drawn, as it will add empty height to the area.
container.hide();
// If the Javascript appends content to `element` that should be drawn, then
// it must also call `container.show()`.
eval(js);
}
CodeCell.prototype.append_text = function (data, element, extra_class) {
var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
// escape ANSI & HTML specials in plaintext:
data = utils.fixConsole(data);
if (extra_class){
toinsert.addClass(extra_class);
}
toinsert.append($("<pre/>").html(data));
element.append(toinsert);
};
CodeCell.prototype.append_svg = function (svg, element) {
var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
toinsert.append(svg);
element.append(toinsert);
};
CodeCell.prototype.append_png = function (png, element) {
var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
element.append(toinsert);
};
CodeCell.prototype.append_jpeg = function (jpeg, element) {
var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
element.append(toinsert);
};
CodeCell.prototype.append_latex = function (latex, element) {
// This method cannot do the typesetting because the latex first has to
// be on the page.
var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
toinsert.append(latex);
element.append(toinsert);
};
CodeCell.prototype.clear_output = function (stdout, stderr, other) {
var that = this;
if (this.clear_out_timeout != null){
// fire previous pending clear *immediately*
clearTimeout(this.clear_out_timeout);
this.clear_out_timeout = null;
this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
}
// store flags for flushing the timeout
this._clear_stdout = stdout;
this._clear_stderr = stderr;
this._clear_other = other;
this.clear_out_timeout = setTimeout(function(){
// really clear timeout only after a short delay
// this reduces flicker in 'clear_output; print' cases
that.clear_out_timeout = null;
that._clear_stdout = that._clear_stderr = that._clear_other = null;
that.clear_output_callback(stdout, stderr, other);
}, 500
);
};
CodeCell.prototype.clear_output_callback = function (stdout, stderr, other) {
var output_div = this.element.find("div.output");
if (stdout && stderr && other){
// clear all, no need for logic
output_div.html("");
this.outputs = [];
return;
}
// remove html output
// each output_subarea that has an identifying class is in an output_area
// which is the element to be removed.
if (stdout){
output_div.find("div.output_stdout").parent().remove();
}
if (stderr){
output_div.find("div.output_stderr").parent().remove();
}
if (other){
output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
}
// remove cleared outputs from JSON list:
for (var i = this.outputs.length - 1; i >= 0; i--){
var out = this.outputs[i];
var output_type = out.output_type;
if (output_type == "display_data" && other){
this.outputs.splice(i,1);
}else if (output_type == "stream"){
if (stdout && out.stream == "stdout"){
this.outputs.splice(i,1);
}else if (stderr && out.stream == "stderr"){
this.outputs.splice(i,1);
}
}
}
};
CodeCell.prototype.clear_input = function () {
this.code_mirror.setValue('');
};
CodeCell.prototype.flush_clear_timeout = function() {
var output_div = this.element.find('div.output');
if (this.clear_out_timeout){
clearTimeout(this.clear_out_timeout);
this.clear_out_timeout = null;
this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
};
}
CodeCell.prototype.collapse = function () {
if (!this.collapsed) {
this.element.find('div.output').hide();
this.collapsed = true;
};
this.output_area.collapse();
};
CodeCell.prototype.expand = function () {
if (this.collapsed) {
this.element.find('div.output').show();
this.collapsed = false;
};
this.output_area.expand();
};
CodeCell.prototype.toggle_output = function () {
if (this.collapsed) {
this.expand();
} else {
this.collapse();
};
this.output_area.toggle_output();
};
CodeCell.prototype.set_input_prompt = function (number) {
this.input_prompt_number = number;
var ns = number || "&nbsp;";
@ -846,6 +221,11 @@ var IPython = (function (IPython) {
};
CodeCell.prototype.clear_input = function () {
this.code_mirror.setValue('');
};
CodeCell.prototype.get_text = function () {
return this.code_mirror.getValue();
};
@ -876,6 +256,13 @@ var IPython = (function (IPython) {
};
CodeCell.prototype.clear_output = function (stdout, stderr, other) {
this.output_area.clear_output(stdout, stderr, other);
};
// JSON serialization
CodeCell.prototype.fromJSON = function (data) {
if (data.cell_type === 'code') {
if (data.input !== undefined) {
@ -886,11 +273,7 @@ var IPython = (function (IPython) {
} else {
this.set_input_prompt();
};
var len = data.outputs.length;
for (var i=0; i<len; i++) {
// append with dynamic=false.
this.append_output(data.outputs[i], false);
};
this.output_area.fromJSON(data.outputs);
if (data.collapsed !== undefined) {
if (data.collapsed) {
this.collapse();
@ -909,11 +292,7 @@ var IPython = (function (IPython) {
if (this.input_prompt_number) {
data.prompt_number = this.input_prompt_number;
};
var outputs = [];
var len = this.outputs.length;
for (var i=0; i<len; i++) {
outputs[i] = this.outputs[i];
};
var outputs = this.output_area.toJSON();
data.outputs = outputs;
data.language = 'python';
data.collapsed = this.collapsed;

@ -0,0 +1,276 @@
// function completer.
//
// completer should be a class that take an cell instance
var IPython = (function (IPython) {
// that will prevent us from misspelling
"use strict";
// easyier key mapping
var key = IPython.utils.keycodes;
// what is the common start of all completions
function shared_start(B) {
if (B.length == 1) {
return B[0];
}
var A = new Array();
for (var i = 0; i < B.length; i++) {
A.push(B[i].str);
}
if (A.length > 1) {
var tem1, tem2, s;
A = A.slice(0).sort();
tem1 = A[0];
s = tem1.length;
tem2 = A.pop();
while (s && tem2.indexOf(tem1) == -1) {
tem1 = tem1.substring(0, --s);
}
if (tem1 == "" || tem2.indexOf(tem1) != 0) {
return null;
}
return {
str: tem1,
type: "computed",
from: B[0].from,
to: B[0].to
};
}
return null;
}
var Completer = function (cell) {
this.editor = cell.code_mirror;
var that = this;
$([IPython.events]).on('status_busy.Kernel', function () {
that.skip_kernel_completion = true;
});
$([IPython.events]).on('status_idle.Kernel', function () {
that.skip_kernel_completion = false;
});
};
Completer.prototype.startCompletion = function () {
// call for a 'first' completion, that will set the editor and do some
// special behaviour like autopicking if only one completion availlable
//
if (this.editor.somethingSelected()) return;
this.done = false;
// use to get focus back on opera
this.carry_on_completion(true);
};
Completer.prototype.carry_on_completion = function (ff) {
// Pass true as parameter if you want the commpleter to autopick when
// only one completion. This function is automatically reinvoked at
// each keystroke with ff = false
var cur = this.editor.getCursor();
var line = this.editor.getLine(cur.line);
var pre_cursor = this.editor.getRange({
line: cur.line,
ch: cur.ch - 1
}, cur);
// we need to check that we are still on a word boundary
// because while typing the completer is still reinvoking itself
if (!/[0-9a-z._]/i.test(pre_cursor)) {
this.close();
return;
}
this.autopick = false;
if (ff != 'undefined' && ff == true) {
this.autopick = true;
}
// We want a single cursor position.
if (this.editor.somethingSelected()) return;
// one kernel completion came back, finish_completing will be called with the results
// we fork here and directly call finish completing if kernel is busy
if (this.skip_kernel_completion == true) {
this.finish_completing({
'matches': [],
matched_text: ""
})
} else {
var callbacks = {
'complete_reply': $.proxy(this.finish_completing, this)
};
IPython.notebook.kernel.complete(line, cur.ch, callbacks);
}
};
Completer.prototype.finish_completing = function (content) {
// let's build a function that wrap all that stuff into what is needed
// for the new completer:
var matched_text = content.matched_text;
var matches = content.matches;
var cur = this.editor.getCursor();
var results = CodeMirror.contextHint(this.editor);
// append the introspection result, in order, at at the beginning of
// the table and compute the replacement range from current cursor
// positon and matched_text length.
for (var i = matches.length - 1; i >= 0; --i) {
results.unshift({
str: matches[i],
type: "introspection",
from: {
line: cur.line,
ch: cur.ch - matched_text.length
},
to: {
line: cur.line,
ch: cur.ch
}
});
}
// one the 2 sources results have been merge, deal with it
this.raw_result = results;
// if empty result return
if (!this.raw_result || !this.raw_result.length) return;
// When there is only one completion, use it directly.
if (this.autopick == true && this.raw_result.length == 1) {
this.insert(this.raw_result[0]);
return;
}
if (this.raw_result.length == 1) {
// test if first and only completion totally matches
// what is typed, in this case dismiss
var str = this.raw_result[0].str;
var pre_cursor = this.editor.getRange({
line: cur.line,
ch: cur.ch - str.length
}, cur);
if (pre_cursor == str) {
this.close();
return;
}
}
this.complete = $('<div/>').addClass('completions');
this.complete.attr('id', 'complete');
this.sel = $('<select/>').attr('multiple', 'true').attr('size', Math.min(10, this.raw_result.length));
var pos = this.editor.cursorCoords();
// TODO: I propose to remove enough horizontal pixel
// to align the text later
this.complete.css('left', pos.x + 'px');
this.complete.css('top', pos.yBot + 'px');
this.complete.append(this.sel);
$('body').append(this.complete);
//build the container
var that = this;
this.sel.dblclick(function () {
that.pick();
});
this.sel.blur(this.close);
this.sel.keydown(function (event) {
that.keydown(event);
});
this.build_gui_list(this.raw_result);
this.sel.focus();
// Opera sometimes ignores focusing a freshly created node
if (window.opera) setTimeout(function () {
if (!this.done) this.sel.focus();
}, 100);
return true;
}
Completer.prototype.insert = function (completion) {
this.editor.replaceRange(completion.str, completion.from, completion.to);
}
Completer.prototype.build_gui_list = function (completions) {
// Need to clear the all list
for (var i = 0; i < completions.length; ++i) {
var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
this.sel.append(opt);
}
this.sel.children().first().attr('selected', 'true');
}
Completer.prototype.close = function () {
if (this.done) return;
this.done = true;
$('.completions').remove();
}
Completer.prototype.pick = function () {
this.insert(this.raw_result[this.sel[0].selectedIndex]);
this.close();
var that = this;
setTimeout(function () {
that.editor.focus();
}, 50);
}
Completer.prototype.keydown = function (event) {
var code = event.keyCode;
var that = this;
// Enter
if (code == key.ENTER) {
CodeMirror.e_stop(event);
this.pick();
}
// Escape or backspace
else if (code == key.ESC) {
CodeMirror.e_stop(event);
this.close();
this.editor.focus();
} else if (code == key.SPACE || code == key.BACKSPACE) {
this.close();
this.editor.focus();
} else if (code == key.TAB) {
//all the fastforwarding operation,
//Check that shared start is not null which can append with prefixed completion
// like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
// to erase py
var sh = shared_start(this.raw_result);
if (sh) {
this.insert(sh);
}
this.close();
CodeMirror.e_stop(event);
this.editor.focus();
//reinvoke self
setTimeout(function () {
that.carry_on_completion();
}, 50);
} else if (code == key.UPARROW || code == key.DOWNARROW) {
// need to do that to be able to move the arrow
// when on the first or last line ofo a code cell
event.stopPropagation();
} else if (code != key.UPARROW && code != key.DOWNARROW) {
this.close();
this.editor.focus();
//we give focus to the editor immediately and call sell in 50 ms
setTimeout(function () {
that.carry_on_completion();
}, 50);
}
}
IPython.Completer = Completer;
return IPython;
}(IPython));

@ -0,0 +1,94 @@
// highly adapted for codemiror jshint
(function () {
"use strict";
function forEach(arr, f) {
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
}
function arrayContains(arr, item) {
if (!Array.prototype.indexOf) {
var i = arr.length;
while (i--) {
if (arr[i] === item) {
return true;
}
}
return false;
}
return arr.indexOf(item) != -1;
}
CodeMirror.contextHint = function (editor) {
// Find the token at the cursor
var cur = editor.getCursor(),
token = editor.getTokenAt(cur),
tprop = token;
// If it's not a 'word-style' token, ignore the token.
// If it is a property, find out what it is a property of.
var list = new Array();
var clist = getCompletions(token, editor);
for (var i = 0; i < clist.length; i++) {
list.push({
str: clist[i],
type: "context",
from: {
line: cur.line,
ch: token.start
},
to: {
line: cur.line,
ch: token.end
}
})
}
return list;
}
// find all 'words' of current cell
var getAllTokens = function (editor) {
var found = [];
// add to found if not already in it
function maybeAdd(str) {
if (!arrayContains(found, str)) found.push(str);
}
// loop through all token on all lines
var lineCount = editor.lineCount();
// loop on line
for (var l = 0; l < lineCount; l++) {
var line = editor.getLine(l);
//loop on char
for (var c = 1; c < line.length; c++) {
var tk = editor.getTokenAt({
line: l,
ch: c
});
// if token has a class, it has geat chances of beeing
// of interest. Add it to the list of possible completions.
// we could skip token of ClassName 'comment'
// or 'number' and 'operator'
if (tk.className != null) {
maybeAdd(tk.string);
}
// jump to char after end of current token
c = tk.end;
}
}
return found;
}
function getCompletions(token, editor) {
var candidates = getAllTokens(editor);
// filter all token that have a common start (but nox exactly) the lenght of the current token
var lambda = function (x) {
return (x.indexOf(token.string) == 0 && x != token.string)
};
var filterd = candidates.filter(lambda);
return filterd;
}
})();

@ -12,7 +12,7 @@
// Give us an object to bind all events to. This object should be created
// before all other objects so it exists when others register event handlers.
// To trigger an event handler:
// $([IPython.events]).trigger('event.Namespace);
// $([IPython.events]).trigger('event.Namespace');
// To handle it:
// $([IPython.events]).on('event.Namespace',function () {});

@ -13,15 +13,18 @@ var IPython = (function (IPython) {
var utils = IPython.utils;
var Kernel = function () {
// Initialization and connection.
var Kernel = function (base_url) {
this.kernel_id = null;
this.shell_channel = null;
this.iopub_channel = null;
this.base_url = $('body').data('baseKernelUrl') + "kernels";
this.base_url = base_url;
this.running = false;
this.username = "username";
this.session_id = utils.uuid();
this._msg_callbacks = {};
if (typeof(WebSocket) !== 'undefined') {
this.WebSocket = WebSocket;
} else if (typeof(MozWebSocket) !== 'undefined') {
@ -32,7 +35,7 @@ var IPython = (function (IPython) {
};
Kernel.prototype.get_msg = function (msg_type, content) {
Kernel.prototype._get_msg = function (msg_type, content) {
var msg = {
header : {
msg_id : utils.uuid(),
@ -46,46 +49,45 @@ var IPython = (function (IPython) {
return msg;
};
Kernel.prototype.start = function (notebook_id, callback) {
Kernel.prototype.start = function (notebook_id) {
var that = this;
if (!this.running) {
var qs = $.param({notebook:notebook_id});
var url = this.base_url + '?' + qs;
$.post(url,
function (kernel_id) {
that._handle_start_kernel(kernel_id, callback);
},
$.proxy(that._kernel_started,that),
'json'
);
};
};
Kernel.prototype.restart = function (callback) {
Kernel.prototype.restart = function () {
$([IPython.events]).trigger('status_restarting.Kernel');
var url = this.kernel_url + "/restart";
var that = this;
if (this.running) {
this.stop_channels();
var url = this.kernel_url + "/restart";
$.post(url,
function (kernel_id) {
that._handle_start_kernel(kernel_id, callback);
},
$.proxy(that._kernel_started, that),
'json'
);
};
};
Kernel.prototype._handle_start_kernel = function (json, callback) {
Kernel.prototype._kernel_started = function (json) {
console.log("Kernel started: ", json.kernel_id);
this.running = true;
this.kernel_id = json.kernel_id;
this.ws_url = json.ws_url;
this.kernel_url = this.base_url + "/" + this.kernel_id;
this.start_channels();
callback();
this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
};
Kernel.prototype._websocket_closed = function(ws_url, early){
var msg;
var parent_item = $('body');
@ -114,7 +116,7 @@ var IPython = (function (IPython) {
}
}
});
};
Kernel.prototype.start_channels = function () {
@ -171,41 +173,104 @@ var IPython = (function (IPython) {
};
};
Kernel.prototype.object_info_request = function (objname) {
// Main public methods.
Kernel.prototype.object_info_request = function (objname, callbacks) {
// When calling this method pass a callbacks structure of the form:
//
// callbacks = {
// 'object_info_reply': object_into_reply_callback
// }
//
// The object_info_reply_callback will be passed the content object of the
// object_into_reply message documented here:
//
// http://ipython.org/ipython-doc/dev/development/messaging.html#object-information
if(typeof(objname)!=null && objname!=null)
{
var content = {
oname : objname.toString(),
};
var msg = this.get_msg("object_info_request", content);
var msg = this._get_msg("object_info_request", content);
this.shell_channel.send(JSON.stringify(msg));
this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
return msg.header.msg_id;
}
return;
}
Kernel.prototype.execute = function (code) {
Kernel.prototype.execute = function (code, callbacks, options) {
// The options object should contain the options for the execute call. Its default
// values are:
//
// options = {
// silent : true,
// user_variables : [],
// user_expressions : {},
// allow_stdin : false
// }
//
// When calling this method pass a callbacks structure of the form:
//
// callbacks = {
// 'execute_reply': execute_reply_callback,
// 'output': output_callback,
// 'clear_output': clear_output_callback,
// 'set_next_input': set_next_input_callback
// }
//
// The execute_reply_callback will be passed the content object of the execute_reply
// message documented here:
//
// http://ipython.org/ipython-doc/dev/development/messaging.html#execute
//
// The output_callback will be passed msg_type ('stream','display_data','pyout','pyerr')
// of the output and the content object of the PUB/SUB channel that contains the
// output:
//
// http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
//
// The clear_output_callback will be passed a content object that contains
// stdout, stderr and other fields that are booleans.
//
// The set_next_input_callback will bepassed the text that should become the next
// input cell.
var content = {
code : code,
silent : false,
silent : true,
user_variables : [],
user_expressions : {},
allow_stdin : false
};
var msg = this.get_msg("execute_request", content);
$.extend(true, content, options)
var msg = this._get_msg("execute_request", content);
this.shell_channel.send(JSON.stringify(msg));
this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
return msg.header.msg_id;
};
Kernel.prototype.complete = function (line, cursor_pos) {
Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
// When calling this method pass a callbacks structure of the form:
//
// callbacks = {
// 'complete_reply': complete_reply_callback
// }
//
// The complete_reply_callback will be passed the content object of the
// complete_reply message documented here:
//
// http://ipython.org/ipython-doc/dev/development/messaging.html#complete
callbacks = callbacks || {};
var content = {
text : '',
line : line,
cursor_pos : cursor_pos
};
var msg = this.get_msg("complete_request", content);
var msg = this._get_msg("complete_request", content);
this.shell_channel.send(JSON.stringify(msg));
this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
return msg.header.msg_id;
};
@ -229,6 +294,91 @@ var IPython = (function (IPython) {
};
};
// Reply handlers.
Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
var callbacks = this._msg_callbacks[msg_id];
return callbacks;
};
Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
this._msg_callbacks[msg_id] = callbacks || {};
}
Kernel.prototype._handle_shell_reply = function (e) {
reply = $.parseJSON(e.data);
var header = reply.header;
var content = reply.content;
var msg_type = header.msg_type;
var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
if (callbacks !== undefined) {
var cb = callbacks[msg_type];
if (cb !== undefined) {
cb(content);
}
};
if (content.payload !== undefined) {
var payload = content.payload || [];
this._handle_payload(callbacks, payload);
}
};
Kernel.prototype._handle_payload = function (callbacks, payload) {
var l = payload.length;
// Payloads are handled by triggering events because we don't want the Kernel
// to depend on the Notebook or Pager classes.
for (var i=0; i<l; i++) {
if (payload[i].source === 'IPython.zmq.page.page') {
var data = {'text':payload[i].text}
$([IPython.events]).trigger('open_with_text.Pager', data);
} else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
if (callbacks.set_next_input !== undefined) {
callbacks.set_next_input(payload[i].text)
}
}
};
};
Kernel.prototype._handle_iopub_reply = function (e) {
reply = $.parseJSON(e.data);
var content = reply.content;
var msg_type = reply.header.msg_type;
var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
if (msg_type !== 'status' && callbacks === undefined) {
// Message not from one of this notebook's cells and there are no
// callbacks to handle it.
return;
}
var output_types = ['stream','display_data','pyout','pyerr'];
if (output_types.indexOf(msg_type) >= 0) {
var cb = callbacks['output'];
if (cb !== undefined) {
cb(msg_type, content);
}
} else if (msg_type === 'status') {
if (content.execution_state === 'busy') {
$([IPython.events]).trigger('status_busy.Kernel');
} else if (content.execution_state === 'idle') {
$([IPython.events]).trigger('status_idle.Kernel');
} else if (content.execution_state === 'dead') {
this.stop_channels();
$([IPython.events]).trigger('status_dead.Kernel');
};
} else if (msg_type === 'clear_output') {
var cb = callbacks['clear_output'];
if (cb !== undefined) {
cb(content);
}
};
};
IPython.Kernel = Kernel;
return IPython;

@ -12,6 +12,7 @@
var IPython = (function (IPython) {
var utils = IPython.utils;
var key = IPython.utils.keycodes;
var Notebook = function (selector) {
this.read_only = IPython.read_only;
@ -23,7 +24,6 @@ var IPython = (function (IPython) {
this.clipboard = null;
this.paste_enabled = false;
this.dirty = false;
this.msg_cell_map = {};
this.metadata = {};
this.control_key_active = false;
this.notebook_id = null;
@ -33,9 +33,6 @@ var IPython = (function (IPython) {
this.style();
this.create_elements();
this.bind_events();
this.set_tooltipontab(true);
this.set_smartcompleter(true);
this.set_timebeforetooltip(1200);
};
@ -63,6 +60,24 @@ var IPython = (function (IPython) {
Notebook.prototype.bind_events = function () {
var that = this;
$([IPython.events]).on('set_next_input.Notebook', function (event, data) {
var index = that.find_cell_index(data.cell);
var new_cell = that.insert_cell_below('code',index);
new_cell.set_text(data.text);
that.dirty = true;
});
$([IPython.events]).on('set_dirty.Notebook', function (event, data) {
that.dirty = data.value;
});
$([IPython.events]).on('select.Cell', function (event, data) {
var index = that.find_cell_index(data.cell);
that.select(index);
});
$(document).keydown(function (event) {
// console.log(event);
if (that.read_only) return true;
@ -73,27 +88,27 @@ var IPython = (function (IPython) {
that.save_notebook();
event.preventDefault();
return false;
} else if (event.which === 27) {
} else if (event.which === key.ESC) {
// Intercept escape at highest level to avoid closing
// websocket connection with firefox
event.preventDefault();
}
if (event.which === 38 && !event.shiftKey) {
if (event.which === key.UPARROW && !event.shiftKey) {
var cell = that.get_selected_cell();
if (cell.at_top()) {
event.preventDefault();
that.select_prev();
};
} else if (event.which === 40 && !event.shiftKey) {
} else if (event.which === key.DOWNARROW && !event.shiftKey) {
var cell = that.get_selected_cell();
if (cell.at_bottom()) {
event.preventDefault();
that.select_next();
};
} else if (event.which === 13 && event.shiftKey) {
} else if (event.which === key.ENTER && event.shiftKey) {
that.execute_selected_cell();
return false;
} else if (event.which === 13 && event.ctrlKey) {
} else if (event.which === key.ENTER && event.ctrlKey) {
that.execute_selected_cell({terminal:true});
return false;
} else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
@ -392,19 +407,6 @@ var IPython = (function (IPython) {
};
Notebook.prototype.cell_for_msg = function (msg_id) {
var cell_id = this.msg_cell_map[msg_id];
var result = null;
this.get_cell_elements().filter(function (index) {
cell = $(this).data("cell");
if (cell.cell_id === cell_id) {
result = cell;
};
});
return result;
};
// Cell selection.
Notebook.prototype.select = function (index) {
@ -527,16 +529,16 @@ var IPython = (function (IPython) {
var cell = null;
if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
if (type === 'code') {
cell = new IPython.CodeCell(this);
cell = new IPython.CodeCell(this.kernel);
cell.set_input_prompt();
} else if (type === 'markdown') {
cell = new IPython.MarkdownCell(this);
cell = new IPython.MarkdownCell();
} else if (type === 'html') {
cell = new IPython.HTMLCell(this);
cell = new IPython.HTMLCell();
} else if (type === 'raw') {
cell = new IPython.RawCell(this);
cell = new IPython.RawCell();
} else if (type === 'heading') {
cell = new IPython.HeadingCell(this);
cell = new IPython.HeadingCell();
};
if (cell !== null) {
if (this.ncells() === 0) {
@ -561,16 +563,16 @@ var IPython = (function (IPython) {
var cell = null;
if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
if (type === 'code') {
cell = new IPython.CodeCell(this);
cell = new IPython.CodeCell(this.kernel);
cell.set_input_prompt();
} else if (type === 'markdown') {
cell = new IPython.MarkdownCell(this);
cell = new IPython.MarkdownCell();
} else if (type === 'html') {
cell = new IPython.HTMLCell(this);
cell = new IPython.HTMLCell();
} else if (type === 'raw') {
cell = new IPython.RawCell(this);
cell = new IPython.RawCell();
} else if (type === 'heading') {
cell = new IPython.HeadingCell(this);
cell = new IPython.HeadingCell();
};
if (cell !== null) {
if (this.ncells() === 0) {
@ -863,21 +865,6 @@ var IPython = (function (IPython) {
};
Notebook.prototype.set_timebeforetooltip = function (time) {
this.time_before_tooltip = time;
};
Notebook.prototype.set_tooltipontab = function (state) {
this.tooltip_on_tab = state;
};
Notebook.prototype.set_smartcompleter = function (state) {
this.smart_completer = state;
};
Notebook.prototype.clear_all_output = function () {
var ncells = this.ncells();
var cells = this.get_cells();
@ -902,8 +889,17 @@ var IPython = (function (IPython) {
// Kernel related things
Notebook.prototype.start_kernel = function () {
this.kernel = new IPython.Kernel();
this.kernel.start(this.notebook_id, $.proxy(this.kernel_started, this));
var base_url = $('body').data('baseKernelUrl') + "kernels";
this.kernel = new IPython.Kernel(base_url);
this.kernel.start(this.notebook_id);
// Now that the kernel has been created, tell the CodeCells about it.
var ncells = this.ncells();
for (var i=0; i<ncells; i++) {
var cell = this.get_cell(i);
if (cell instanceof IPython.CodeCell) {
cell.set_kernel(this.kernel)
};
};
};
@ -919,117 +915,7 @@ var IPython = (function (IPython) {
closeText: '',
buttons : {
"Restart": function () {
that.kernel.restart($.proxy(that.kernel_started, that));
$(this).dialog('close');
},
"Continue running": function () {
$(this).dialog('close');
}
}
});
};
Notebook.prototype.kernel_started = function () {
console.log("Kernel started: ", this.kernel.kernel_id);
this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
};
Notebook.prototype.handle_shell_reply = function (e) {
reply = $.parseJSON(e.data);
var header = reply.header;
var content = reply.content;
var msg_type = header.msg_type;
// console.log(reply);
var cell = this.cell_for_msg(reply.parent_header.msg_id);
if (msg_type === "execute_reply") {
cell.set_input_prompt(content.execution_count);
cell.element.removeClass("running");
this.dirty = true;
} else if (msg_type === "complete_reply") {
cell.finish_completing(content.matched_text, content.matches);
} else if (msg_type === "object_info_reply"){
//console.log('back from object_info_request : ')
rep = reply.content;
if(rep.found)
{
cell.finish_tooltip(rep);
}
} else {
//console.log("unknown reply:"+msg_type);
}
// when having a rely from object_info_reply,
// no payload so no nned to handle it
if(typeof(content.payload)!='undefined') {
var payload = content.payload || [];
this.handle_payload(cell, payload);
}
};
Notebook.prototype.handle_payload = function (cell, payload) {
var l = payload.length;
for (var i=0; i<l; i++) {
if (payload[i].source === 'IPython.zmq.page.page') {
if (payload[i].text.trim() !== '') {
IPython.pager.clear();
IPython.pager.expand();
IPython.pager.append_text(payload[i].text);
}
} else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
var index = this.find_cell_index(cell);
var new_cell = this.insert_cell_below('code',index);
new_cell.set_text(payload[i].text);
this.dirty = true;
}
};
};
Notebook.prototype.handle_iopub_reply = function (e) {
reply = $.parseJSON(e.data);
var content = reply.content;
// console.log(reply);
var msg_type = reply.header.msg_type;
var cell = this.cell_for_msg(reply.parent_header.msg_id);
if (msg_type !== 'status' && !cell){
// message not from this notebook, but should be attached to a cell
// console.log("Received IOPub message not caused by one of my cells");
// console.log(reply);
return;
}
var output_types = ['stream','display_data','pyout','pyerr'];
if (output_types.indexOf(msg_type) >= 0) {
this.handle_output(cell, msg_type, content);
} else if (msg_type === 'status') {
if (content.execution_state === 'busy') {
$([IPython.events]).trigger('status_busy.Kernel');
} else if (content.execution_state === 'idle') {
$([IPython.events]).trigger('status_idle.Kernel');
} else if (content.execution_state === 'dead') {
this.handle_status_dead();
};
} else if (msg_type === 'clear_output') {
cell.clear_output(content.stdout, content.stderr, content.other);
};
};
Notebook.prototype.handle_status_dead = function () {
var that = this;
this.kernel.stop_channels();
var dialog = $('<div/>');
dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
$(document).append(dialog);
dialog.dialog({
resizable: false,
modal: true,
title: "Dead kernel",
buttons : {
"Restart": function () {
that.start_kernel();
that.kernel.restart();
$(this).dialog('close');
},
"Continue running": function () {
@ -1040,57 +926,6 @@ var IPython = (function (IPython) {
};
Notebook.prototype.handle_output = function (cell, msg_type, content) {
var json = {};
json.output_type = msg_type;
if (msg_type === "stream") {
json.text = content.data;
json.stream = content.name;
} else if (msg_type === "display_data") {
json = this.convert_mime_types(json, content.data);
} else if (msg_type === "pyout") {
json.prompt_number = content.execution_count;
json = this.convert_mime_types(json, content.data);
} else if (msg_type === "pyerr") {
json.ename = content.ename;
json.evalue = content.evalue;
json.traceback = content.traceback;
};
// append with dynamic=true
cell.append_output(json, true);
this.dirty = true;
};
Notebook.prototype.convert_mime_types = function (json, data) {
if (data['text/plain'] !== undefined) {
json.text = data['text/plain'];
};
if (data['text/html'] !== undefined) {
json.html = data['text/html'];
};
if (data['image/svg+xml'] !== undefined) {
json.svg = data['image/svg+xml'];
};
if (data['image/png'] !== undefined) {
json.png = data['image/png'];
};
if (data['image/jpeg'] !== undefined) {
json.jpeg = data['image/jpeg'];
};
if (data['text/latex'] !== undefined) {
json.latex = data['text/latex'];
};
if (data['application/json'] !== undefined) {
json.json = data['application/json'];
};
if (data['application/javascript'] !== undefined) {
json.javascript = data['application/javascript'];
}
return json;
};
Notebook.prototype.execute_selected_cell = function (options) {
// add_new: should a new cell be added if we are at the end of the nb
// terminal: execute in terminal mode, which stays in the current cell
@ -1100,12 +935,7 @@ var IPython = (function (IPython) {
var cell = that.get_selected_cell();
var cell_index = that.find_cell_index(cell);
if (cell instanceof IPython.CodeCell) {
cell.clear_output(true, true, true);
cell.set_input_prompt('*');
cell.element.addClass("running");
var code = cell.get_text();
var msg_id = that.kernel.execute(cell.get_text());
that.msg_cell_map[msg_id] = cell.cell_id;
cell.execute();
} else if (cell instanceof IPython.HTMLCell) {
cell.render();
}
@ -1133,37 +963,6 @@ var IPython = (function (IPython) {
this.scroll_to_bottom();
};
Notebook.prototype.request_tool_tip = function (cell,func) {
// Feel free to shorten this logic if you are better
// than me in regEx
// basicaly you shoul be able to get xxx.xxx.xxx from
// something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
// remove everything between matchin bracket (need to iterate)
matchBracket = /\([^\(\)]+\)/g;
oldfunc = func;
func = func.replace(matchBracket,"");
while( oldfunc != func )
{
oldfunc = func;
func = func.replace(matchBracket,"");
}
// remove everythin after last open bracket
endBracket = /\([^\(]*$/g;
func = func.replace(endBracket,"");
var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive
var msg_id = this.kernel.object_info_request(re.exec(func));
if(typeof(msg_id)!='undefined'){
this.msg_cell_map[msg_id] = cell.cell_id;
}
};
Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
var msg_id = this.kernel.complete(line, cursor_pos);
this.msg_cell_map[msg_id] = cell.cell_id;
};
// Persistance and loading
Notebook.prototype.get_notebook_id = function () {
@ -1226,11 +1025,11 @@ var IPython = (function (IPython) {
Notebook.prototype.toJSON = function () {
var cells = this.get_cells();
var ncells = cells.length;
cell_array = new Array(ncells);
var cell_array = new Array(ncells);
for (var i=0; i<ncells; i++) {
cell_array[i] = cells[i].toJSON();
};
data = {
var data = {
// Only handle 1 worksheet for now.
worksheets : [{cells:cell_array}],
metadata : this.metadata
@ -1294,9 +1093,6 @@ var IPython = (function (IPython) {
this.insert_cell_below('code');
};
this.dirty = false;
if (! this.read_only) {
this.start_kernel();
}
this.select(0);
this.scroll_to_top();
if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
@ -1323,6 +1119,11 @@ var IPython = (function (IPython) {
width: 400
});
}
// Create the kernel after the notebook is completely loaded to prevent
// code execution upon loading, which is a security risk.
if (! this.read_only) {
this.start_kernel();
}
$([IPython.events]).trigger('notebook_loaded.Notebook');
};

@ -30,6 +30,7 @@ $(document).ready(function () {
IPython.save_widget = new IPython.SaveWidget('span#save_widget');
IPython.menubar = new IPython.MenuBar('#menubar')
IPython.toolbar = new IPython.ToolBar('#toolbar')
IPython.tooltip = new IPython.Tooltip()
IPython.notification_widget = new IPython.NotificationWidget('#notification')
IPython.layout_manager.do_resize();

@ -48,11 +48,32 @@ var IPython = (function (IPython) {
that.set_message("Kernel busy");
});
$([IPython.events]).on('status_restarting.Kernel',function () {
IPython.save_widget.update_document_title();
that.set_message("Restarting kernel",500);
});
$([IPython.events]).on('status_interrupting.Kernel',function () {
that.set_message("Interrupting kernel",500);
});
$([IPython.events]).on('status_dead.Kernel',function () {
var dialog = $('<div/>');
dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
$(document).append(dialog);
dialog.dialog({
resizable: false,
modal: true,
title: "Dead kernel",
buttons : {
"Restart": function () {
$([IPython.events]).trigger('status_restarting.Kernel');
IPython.notebook.start_kernel();
$(this).dialog('close');
},
"Continue running": function () {
$(this).dialog('close');
}
}
});
});
// Notebook events
$([IPython.events]).on('notebook_loading.Notebook', function () {
that.set_message("Loading notebook",500);

@ -0,0 +1,403 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// OutputArea
//============================================================================
var IPython = (function (IPython) {
"use strict";
var utils = IPython.utils;
var OutputArea = function (selector, prompt_area) {
this.selector = selector;
this.element = $(selector);
this.outputs = [];
this.collapsed = false;
this.clear_out_timeout = null;
if (prompt_area === undefined) {
this.prompt_area = true;
} else {
this.prompt_area = prompt_area;
};
this.style();
};
OutputArea.prototype.style = function () {
this.element.addClass('output vbox');
this.collapse();
};
OutputArea.prototype.collapse = function () {
if (!this.collapsed) {
this.element.hide();
this.collapsed = true;
};
};
OutputArea.prototype.expand = function () {
if (this.collapsed) {
this.element.show();
this.collapsed = false;
};
};
OutputArea.prototype.toggle_output = function () {
if (this.collapsed) {
this.expand();
} else {
this.collapse();
};
};
// typeset with MathJax if MathJax is available
OutputArea.prototype.typeset = function () {
if (window.MathJax){
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
}
};
OutputArea.prototype.handle_output = function (msg_type, content) {
var json = {};
json.output_type = msg_type;
if (msg_type === "stream") {
json.text = content.data;
json.stream = content.name;
} else if (msg_type === "display_data") {
json = this.convert_mime_types(json, content.data);
} else if (msg_type === "pyout") {
json.prompt_number = content.execution_count;
json = this.convert_mime_types(json, content.data);
} else if (msg_type === "pyerr") {
json.ename = content.ename;
json.evalue = content.evalue;
json.traceback = content.traceback;
};
// append with dynamic=true
this.append_output(json, true);
};
OutputArea.prototype.convert_mime_types = function (json, data) {
if (data['text/plain'] !== undefined) {
json.text = data['text/plain'];
};
if (data['text/html'] !== undefined) {
json.html = data['text/html'];
};
if (data['image/svg+xml'] !== undefined) {
json.svg = data['image/svg+xml'];
};
if (data['image/png'] !== undefined) {
json.png = data['image/png'];
};
if (data['image/jpeg'] !== undefined) {
json.jpeg = data['image/jpeg'];
};
if (data['text/latex'] !== undefined) {
json.latex = data['text/latex'];
};
if (data['application/json'] !== undefined) {
json.json = data['application/json'];
};
if (data['application/javascript'] !== undefined) {
json.javascript = data['application/javascript'];
}
return json;
};
OutputArea.prototype.append_output = function (json, dynamic) {
// If dynamic is true, javascript output will be eval'd.
this.expand();
this.flush_clear_timeout();
if (json.output_type === 'pyout') {
this.append_pyout(json, dynamic);
} else if (json.output_type === 'pyerr') {
this.append_pyerr(json);
} else if (json.output_type === 'display_data') {
this.append_display_data(json, dynamic);
} else if (json.output_type === 'stream') {
this.append_stream(json);
};
this.outputs.push(json);
};
OutputArea.prototype.create_output_area = function () {
var oa = $("<div/>").addClass("hbox output_area");
if (this.prompt_area) {
oa.append($('<div/>').addClass('prompt'));
}
return oa;
};
OutputArea.prototype.append_pyout = function (json, dynamic) {
var n = json.prompt_number || ' ';
var toinsert = this.create_output_area();
if (this.prompt_area) {
toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
}
this.append_mime_type(json, toinsert, dynamic);
this.element.append(toinsert);
// If we just output latex, typeset it.
if ((json.latex !== undefined) || (json.html !== undefined)) {
this.typeset();
};
};
OutputArea.prototype.append_pyerr = function (json) {
var tb = json.traceback;
if (tb !== undefined && tb.length > 0) {
var s = '';
var len = tb.length;
for (var i=0; i<len; i++) {
s = s + tb[i] + '\n';
}
s = s + '\n';
var toinsert = this.create_output_area();
this.append_text(s, toinsert);
this.element.append(toinsert);
};
};
OutputArea.prototype.append_stream = function (json) {
// temporary fix: if stream undefined (json file written prior to this patch),
// default to most likely stdout:
if (json.stream == undefined){
json.stream = 'stdout';
}
if (!utils.fixConsole(json.text)){
// fixConsole gives nothing (empty string, \r, etc.)
// so don't append any elements, which might add undesirable space
return;
}
var subclass = "output_"+json.stream;
if (this.outputs.length > 0){
// have at least one output to consider
var last = this.outputs[this.outputs.length-1];
if (last.output_type == 'stream' && json.stream == last.stream){
// latest output was in the same stream,
// so append directly into its pre tag
// escape ANSI & HTML specials:
var text = utils.fixConsole(json.text);
this.element.find('div.'+subclass).last().find('pre').append(text);
return;
}
}
// If we got here, attach a new div
var toinsert = this.create_output_area();
this.append_text(json.text, toinsert, "output_stream "+subclass);
this.element.append(toinsert);
};
OutputArea.prototype.append_display_data = function (json, dynamic) {
var toinsert = this.create_output_area();
this.append_mime_type(json, toinsert, dynamic);
this.element.append(toinsert);
// If we just output latex, typeset it.
if ( (json.latex !== undefined) || (json.html !== undefined) ) {
this.typeset();
};
};
OutputArea.prototype.append_mime_type = function (json, element, dynamic) {
if (json.javascript !== undefined && dynamic) {
this.append_javascript(json.javascript, element, dynamic);
} else if (json.html !== undefined) {
this.append_html(json.html, element);
} else if (json.latex !== undefined) {
this.append_latex(json.latex, element);
} else if (json.svg !== undefined) {
this.append_svg(json.svg, element);
} else if (json.png !== undefined) {
this.append_png(json.png, element);
} else if (json.jpeg !== undefined) {
this.append_jpeg(json.jpeg, element);
} else if (json.text !== undefined) {
this.append_text(json.text, element);
};
};
OutputArea.prototype.append_html = function (html, element) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_html rendered_html");
toinsert.append(html);
element.append(toinsert);
};
OutputArea.prototype.append_javascript = function (js, container) {
// We just eval the JS code, element appears in the local scope.
var element = $("<div/>").addClass("box-flex1 output_subarea");
container.append(element);
// Div for js shouldn't be drawn, as it will add empty height to the area.
container.hide();
// If the Javascript appends content to `element` that should be drawn, then
// it must also call `container.show()`.
eval(js);
}
OutputArea.prototype.append_text = function (data, element, extra_class) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_text");
// escape ANSI & HTML specials in plaintext:
data = utils.fixConsole(data);
if (extra_class){
toinsert.addClass(extra_class);
}
toinsert.append($("<pre/>").html(data));
element.append(toinsert);
};
OutputArea.prototype.append_svg = function (svg, element) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_svg");
toinsert.append(svg);
element.append(toinsert);
};
OutputArea.prototype.append_png = function (png, element) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_png");
toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
element.append(toinsert);
};
OutputArea.prototype.append_jpeg = function (jpeg, element) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_jpeg");
toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
element.append(toinsert);
};
OutputArea.prototype.append_latex = function (latex, element) {
// This method cannot do the typesetting because the latex first has to
// be on the page.
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_latex");
toinsert.append(latex);
element.append(toinsert);
};
OutputArea.prototype.handle_clear_output = function (content) {
this.clear_output(content.stdout, content.stderr, content.other);
}
OutputArea.prototype.clear_output = function (stdout, stderr, other) {
var that = this;
if (this.clear_out_timeout != null){
// fire previous pending clear *immediately*
clearTimeout(this.clear_out_timeout);
this.clear_out_timeout = null;
this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
}
// store flags for flushing the timeout
this._clear_stdout = stdout;
this._clear_stderr = stderr;
this._clear_other = other;
this.clear_out_timeout = setTimeout(function() {
// really clear timeout only after a short delay
// this reduces flicker in 'clear_output; print' cases
that.clear_out_timeout = null;
that._clear_stdout = that._clear_stderr = that._clear_other = null;
that.clear_output_callback(stdout, stderr, other);
}, 500
);
};
OutputArea.prototype.clear_output_callback = function (stdout, stderr, other) {
var output_div = this.element;
if (stdout && stderr && other){
// clear all, no need for logic
output_div.html("");
this.outputs = [];
return;
}
// remove html output
// each output_subarea that has an identifying class is in an output_area
// which is the element to be removed.
if (stdout) {
output_div.find("div.output_stdout").parent().remove();
}
if (stderr) {
output_div.find("div.output_stderr").parent().remove();
}
if (other) {
output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
}
// remove cleared outputs from JSON list:
for (var i = this.outputs.length - 1; i >= 0; i--) {
var out = this.outputs[i];
var output_type = out.output_type;
if (output_type == "display_data" && other) {
this.outputs.splice(i,1);
} else if (output_type == "stream") {
if (stdout && out.stream == "stdout") {
this.outputs.splice(i,1);
} else if (stderr && out.stream == "stderr") {
this.outputs.splice(i,1);
}
}
}
};
OutputArea.prototype.flush_clear_timeout = function() {
var output_div = this.element;
if (this.clear_out_timeout){
clearTimeout(this.clear_out_timeout);
this.clear_out_timeout = null;
this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
};
}
// JSON serialization
OutputArea.prototype.fromJSON = function (outputs) {
var len = outputs.length;
for (var i=0; i<len; i++) {
// append with dynamic=false.
this.append_output(outputs[i], false);
};
};
OutputArea.prototype.toJSON = function () {
var outputs = [];
var len = this.outputs.length;
for (var i=0; i<len; i++) {
outputs[i] = this.outputs[i];
};
return outputs;
};
IPython.OutputArea = OutputArea;
return IPython;
}(IPython));

@ -75,6 +75,13 @@ var IPython = (function (IPython) {
that.toggle();
});
$([IPython.events]).on('open_with_text.Pager', function (event, data) {
if (data.text.trim() !== '') {
that.clear();
that.expand();
that.append_text(data.text);
};
});
};

@ -13,7 +13,7 @@ var IPython = (function (IPython) {
// TextCell base class
var TextCell = function (notebook) {
var TextCell = function () {
this.code_mirror_mode = this.code_mirror_mode || 'htmlmixed';
IPython.Cell.apply(this, arguments);
this.rendered = false;
@ -176,7 +176,7 @@ var IPython = (function (IPython) {
// HTMLCell
var HTMLCell = function (notebook) {
var HTMLCell = function () {
this.placeholder = "Type <strong>HTML</strong> and LaTeX: $\\alpha^2$";
IPython.TextCell.apply(this, arguments);
this.cell_type = 'html';
@ -201,7 +201,7 @@ var IPython = (function (IPython) {
// MarkdownCell
var MarkdownCell = function (notebook) {
var MarkdownCell = function () {
this.placeholder = "Type *Markdown* and LaTeX: $\\alpha^2$";
IPython.TextCell.apply(this, arguments);
this.cell_type = 'markdown';
@ -239,7 +239,7 @@ var IPython = (function (IPython) {
// RawCell
var RawCell = function (notebook) {
var RawCell = function () {
this.placeholder = "Type plain text and LaTeX: $\\alpha^2$";
this.code_mirror_mode = 'rst';
IPython.TextCell.apply(this, arguments);
@ -285,7 +285,7 @@ var IPython = (function (IPython) {
// HTMLCell
var HeadingCell = function (notebook) {
var HeadingCell = function () {
this.placeholder = "Type Heading Here";
IPython.TextCell.apply(this, arguments);
this.cell_type = 'heading';

@ -0,0 +1,366 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// Tooltip
//============================================================================
//
// you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
//
// you can configure the differents action of pressing tab several times in a row by
// setting/appending different fonction in the array
// IPython.tooltip.tabs_functions
//
// eg :
// IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
//
var IPython = (function (IPython) {
"use strict";
var utils = IPython.utils;
// tooltip constructor
var Tooltip = function () {
var that = this;
this.time_before_tooltip = 1200;
// handle to html
this.tooltip = $('#tooltip');
this._hidden = true;
// variable for consecutive call
this._old_cell = null;
this._old_request = null;
this._consecutive_counter = 0;
// 'sticky ?'
this._sticky = false;
// contain the button in the upper right corner
this.buttons = $('<div/>').addClass('tooltipbuttons');
// will contain the docstring
this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
// build the buttons menu on the upper right
// expand the tooltip to see more
var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
.attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press tab 2 times)').click(function () {
that.expand()
}).append(
$('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
// open in pager
var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)');
var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
morelink.append(morespan);
morelink.click(function () {
that.showInPager();
});
// close the tooltip
var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
closelink.append(closespan);
closelink.click(function () {
that.remove_and_cancel_tooltip(true);
});
this._clocklink = $('<a/>').attr('href', "#");
this._clocklink.attr('role', "button");
this._clocklink.addClass('ui-button');
this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
var clockspan = $('<span/>').text('Close');
clockspan.addClass('ui-icon');
clockspan.addClass('ui-icon-clock');
this._clocklink.append(clockspan);
this._clocklink.click(function () {
that.cancel_stick();
});
//construct the tooltip
// add in the reverse order you want them to appear
this.buttons.append(closelink);
this.buttons.append(expandlink);
this.buttons.append(morelink);
this.buttons.append(this._clocklink);
this._clocklink.hide();
// we need a phony element to make the small arrow
// of the tooltip in css
// we will move the arrow later
this.arrow = $('<div/>').addClass('pretooltiparrow');
this.tooltip.append(this.buttons);
this.tooltip.append(this.arrow);
this.tooltip.append(this.text);
// function that will be called if you press tab 1, 2, 3... times in a row
this.tabs_functions = [function (cell, text) {
that._request_tooltip(cell, text);
IPython.notification_widget.set_message('tab again to expand pager', 2500);
}, function () {
that.expand();
IPython.notification_widget.set_message('tab again to make pager sticky for 10s', 2500);
}, function () {
that.stick();
IPython.notification_widget.set_message('tab again to open help in pager', 2500);
}, function (cell) {
that.cancel_stick();
that.showInPager(cell);
that._cmfocus();
}];
// call after all the tabs function above have bee call to clean their effects
// if necessary
this.reset_tabs_function = function (cell, text) {
this._old_cell = (cell) ? cell : null;
this._old_request = (text) ? text : null;
this._consecutive_counter = 0;
}
};
Tooltip.prototype.showInPager = function (cell) {
// reexecute last call in pager by appending ? to show back in pager
var that = this;
var empty = function () {};
IPython.notebook.kernel.execute(
that.name + '?', {
'execute_reply': empty,
'output': empty,
'clear_output': empty,
'cell': cell
}, {
'silent': false
});
this.remove_and_cancel_tooltip();
this._cmfocus();
}
// grow the tooltip verticaly
Tooltip.prototype.expand = function () {
this.text.removeClass('smalltooltip');
this.text.addClass('bigtooltip');
$('#expanbutton').hide('slow');
this._cmfocus();
}
// deal with all the logic of hiding the tooltip
// and reset it's status
Tooltip.prototype._hide = function () {
this.tooltip.fadeOut('fast');
$('#expanbutton').show('slow');
this.text.removeClass('bigtooltip');
this.text.addClass('smalltooltip');
// keep scroll top to be sure to always see the first line
this.text.scrollTop(0);
this._hidden = true;
this.code_mirror = null;
}
Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
// note that we don't handle closing directly inside the calltip
// as in the completer, because it is not focusable, so won't
// get the event.
if (this._sticky == false || force == true) {
this.cancel_stick();
this._hide();
}
this.cancel_pending();
this.reset_tabs_function();
this._cmfocus();
}
// cancel autocall done after '(' for example.
Tooltip.prototype.cancel_pending = function () {
if (this._tooltip_timeout != null) {
clearTimeout(this._tooltip_timeout);
this._tooltip_timeout = null;
}
}
// will trigger tooltip after timeout
Tooltip.prototype.pending = function (cell) {
var that = this;
this._tooltip_timeout = setTimeout(function () {
that.request(cell)
}, that.time_before_tooltip);
}
Tooltip.prototype._request_tooltip = function (cell, func) {
// use internally just to make the request to the kernel
// Feel free to shorten this logic if you are better
// than me in regEx
// basicaly you shoul be able to get xxx.xxx.xxx from
// something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
// remove everything between matchin bracket (need to iterate)
var matchBracket = /\([^\(\)]+\)/g;
var endBracket = /\([^\(]*$/g;
var oldfunc = func;
func = func.replace(matchBracket, "");
while (oldfunc != func) {
oldfunc = func;
func = func.replace(matchBracket, "");
}
// remove everything after last open bracket
func = func.replace(endBracket, "");
var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive
var callbacks = {
'object_info_reply': $.proxy(this._show, this)
}
var msg_id = IPython.notebook.kernel.object_info_request(re.exec(func), callbacks);
}
// make an imediate completion request
Tooltip.prototype.request = function (cell) {
// request(codecell)
// Deal with extracting the text from the cell and counting
// call in a row
this.cancel_pending();
var editor = cell.code_mirror;
var cursor = editor.getCursor();
var text = editor.getRange({
line: cursor.line,
ch: 0
}, cursor).trim();
// need a permanent handel to code_mirror for future auto recall
this.code_mirror = editor;
// now we treat the different number of keypress
// first if same cell, same text, increment counter by 1
if (this._old_cell == cell && this._old_request == text && this._hidden == false) {
this._consecutive_counter++;
} else {
// else reset
this.cancel_stick();
this.reset_tabs_function (cell, text);
}
// don't do anything if line beggin with '(' or is empty
if (text === "" || text === "(") {
return;
}
this.tabs_functions[this._consecutive_counter](cell, text);
// then if we are at the end of list function, reset
if (this._consecutive_counter == this.tabs_functions.length) this.reset_tabs_function (cell, text);
return;
}
// cancel the option of having the tooltip to stick
Tooltip.prototype.cancel_stick = function () {
clearTimeout(this._stick_timeout);
this._stick_timeout = null;
this._clocklink.hide('slow');
this._sticky = false;
}
// put the tooltip in a sicky state for 10 seconds
// it won't be removed by remove_and_cancell() unless you called with
// the first parameter set to true.
// remove_and_cancell_tooltip(true)
Tooltip.prototype.stick = function (time) {
time = (time != undefined) ? time : 10;
var that = this;
this._sticky = true;
this._clocklink.show('slow');
this._stick_timeout = setTimeout(function () {
that._sticky = false;
that._clocklink.hide('slow');
}, time * 1000);
}
// should be called with the kernel reply to actually show the tooltip
Tooltip.prototype._show = function (reply) {
// move the bubble if it is not hidden
// otherwise fade it
this.name = reply.name;
// do some math to have the tooltip arrow on more or less on left or right
// width of the editor
var w = $(this.code_mirror.getScrollerElement()).width();
// ofset of the editor
var o = $(this.code_mirror.getScrollerElement()).offset();
var pos = this.code_mirror.cursorCoords();
var xinit = pos.x;
var xinter = o.left + (xinit - o.left) / w * (w - 450);
var posarrowleft = xinit - xinter;
if (this._hidden == false) {
this.tooltip.animate({
'left': xinter - 30 + 'px',
'top': (pos.yBot + 10) + 'px'
});
} else {
this.tooltip.css({
'left': xinter - 30 + 'px'
});
this.tooltip.css({
'top': (pos.yBot + 10) + 'px'
});
}
this.arrow.animate({
'left': posarrowleft + 'px'
});
this.tooltip.fadeIn('fast');
this._hidden = false;
// build docstring
var defstring = reply.call_def;
if (defstring == null) {
defstring = reply.init_definition;
}
if (defstring == null) {
defstring = reply.definition;
}
var docstring = reply.call_docstring;
if (docstring == null) {
docstring = reply.init_docstring;
}
if (docstring == null) {
docstring = reply.docstring;
}
if (docstring == null) {
docstring = "<empty docstring>";
}
this.text.children().remove();
var pre = $('<pre/>').html(utils.fixConsole(docstring));
if (defstring) {
var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
this.text.append(defstring_html);
}
this.text.append(pre);
// keep scroll top to be sure to always see the first line
this.text.scrollTop(0);
}
// convenient funciton to have the correct code_mirror back into focus
Tooltip.prototype._cmfocus = function () {
var cm = this.code_mirror;
if (cm == IPython.notebook.get_selected_cell())
{
setTimeout(function () {
cm.focus();
}, 50);
}
}
IPython.Tooltip = Tooltip;
return IPython;
}(IPython));

@ -91,11 +91,38 @@ IPython.utils = (function (IPython) {
}
};
// some keycodes that seem to be platform/browser independant
var keycodes ={
BACKSPACE: 8,
TAB : 9,
ENTER : 13,
SHIFT : 16,
CTRL : 17,
CONTROL : 17,
ALT : 18,
ESC : 27,
SPACE : 32,
PGUP : 33,
PGDOWN : 34,
LEFT_ARROW: 37,
LEFTARROW: 37,
LEFT : 37,
UP_ARROW : 38,
UPARROW : 38,
UP : 38,
RIGHT_ARROW:39,
RIGHTARROW:39,
RIGHT : 39,
DOWN_ARROW: 40,
DOWNARROW: 40,
DOWN : 40,
};
return {
uuid : uuid,
fixConsole : fixConsole,
grow : grow
keycodes : keycodes,
grow : grow,
};
}(IPython));

@ -1,5 +1,4 @@
{% extends page.html %}
{% block stylesheet %}
{% if mathjax_url %}
@ -17,6 +16,7 @@ window.mathjax_url = "{{mathjax_url}}";
<link rel="stylesheet" href="{{ static_url("prettify/prettify.css") }}"/>
<link rel="stylesheet" href="{{ static_url("css/notebook.css") }}" type="text/css" />
<link rel="stylesheet" href="{{ static_url("css/tooltip.css") }}" type="text/css" />
<link rel="stylesheet" href="{{ static_url("css/renderedhtml.css") }}" type="text/css" />
{% end %}
@ -194,6 +194,8 @@ data-notebook-id={{notebook_id}}
</div>
</div>
<div id='tooltip' class='tooltip ui-corner-all' style='display:none'></div>
{% end %}
@ -218,8 +220,10 @@ data-notebook-id={{notebook_id}}
<script src="{{ static_url("js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/initmathjax.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
@ -229,7 +233,9 @@ data-notebook-id={{notebook_id}}
<script src="{{ static_url("js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/notebookmain.js") }}" type="text/javascript" charset="utf-8"></script>
{% end %}
<script src="{{ static_url("js/contexthint.js") }} charset="utf-8"></script>
{% end %}

@ -0,0 +1,38 @@
{
"metadata": {
"name": "directview"
},
"nbformat": 3,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"input": [
"from directview import interact",
"from IPython.parallel import Client"
],
"language": "python",
"outputs": []
},
{
"cell_type": "code",
"input": [
"c = Client()",
"dv = c[:]"
],
"language": "python",
"outputs": []
},
{
"cell_type": "code",
"input": [
"interact(dv)"
],
"language": "python",
"outputs": []
}
]
}
]
}
Loading…
Cancel
Save