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
commit
ac548bd8bc
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
})();
|
||||
@ -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));
|
||||
@ -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));
|
||||
@ -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…
Reference in new issue