From bb809a0689d2f5215288b26f4321b6a0201fd194 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 25 Jun 2015 01:08:43 -0400 Subject: [PATCH] switch to IPython v3 protocol, require IPython 3.0 or later --- REQUIRE | 1 - deps/build.jl | 129 +---------- deps/custom.js | 471 ----------------------------------------- deps/julia.js | 292 ------------------------- src/IJulia.jl | 15 +- src/execute_request.jl | 65 +++--- src/handlers.jl | 123 +++++++---- src/msg.jl | 12 +- src/stdio.jl | 4 +- 9 files changed, 139 insertions(+), 973 deletions(-) delete mode 100644 deps/custom.js delete mode 100644 deps/julia.js diff --git a/REQUIRE b/REQUIRE index 0f14f8f8..fc3d4ee7 100644 --- a/REQUIRE +++ b/REQUIRE @@ -2,5 +2,4 @@ julia 0.3 Nettle JSON 0.2- ZMQ 0.1- -REPLCompletions Compat 0.4 diff --git a/deps/build.jl b/deps/build.jl index 11d7f729..cae984bd 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -11,99 +11,13 @@ ENV["PYTHONIOENCODING"] = "UTF-8" include("ipython.jl") const ipython, ipyvers = find_ipython() -if ipyvers < v"1.0.0-dev" - error("IPython 1.0 or later is required for IJulia, got $ipyvers instead") +if ipyvers < v"3.0" + error("IPython 3.0 or later is required for IJulia, got $ipyvers instead") else eprintln("Found IPython version $ipyvers ... ok.") end ####################################################################### -# Create Julia profile for IPython and fix the config options. - -# create julia profile (no-op if we already have one) -eprintln("Creating julia profile in IPython...") -run(`$ipython profile create julia`) - -juliaprof = chomp(readall(`$ipython locate profile julia`)) - -# set c.$s in prof file to val, or nothing if it is already set -# unless overwrite is true -function add_config(prof::String, s::String, val; overwrite::Bool=false) - p = joinpath(juliaprof, prof) - r = Regex(string("^[ \\t]*c\\.", replace(s, r"\.", "\\."), "\\s*=.*\$"), "m") - if isfile(p) - c = readall(p) - if ismatch(r, c) - m = replace(match(r, c).match, r"\s*$", "") - if !overwrite || m[search(m,'c'):end] == "c.$s = $val" - eprintln("(Existing $s setting in $prof is untouched.)") - else - eprintln("Changing $s to $val in $prof...") - open(p, "w") do f - print(f, replace(c, r, old -> "# $old")) - print(f, """ -c.$s = $val -""") - end - end - else - eprintln("Adding $s = $val to $prof...") - open(p, "a") do f - print(f, """ - -c.$s = $val -""") - end - end - else - eprintln("Creating $prof with $s = $val...") - open(p, "w") do f - print(f, """ -c = get_config() -c.$s = $val -""") - end - end -end - -# add Julia kernel manager if we don't have one yet -if VERSION >= v"0.3-" - binary_name = @windows? "julia.exe":"julia" -else - binary_name = @windows? "julia.bat":"julia-basic" -end - -kernelcmd_array = [escape_string(joinpath(JULIA_HOME,("$binary_name")))] - -if VERSION >= v"0.3" - push!(kernelcmd_array,"-i") -end - -# Can be used by packaging script to set correct system-wise install path -ijulia_dir = get(ENV, "IJULIA_DIR", Pkg.dir("IJulia")) -append!(kernelcmd_array, ["-F", escape_string(joinpath(ijulia_dir,"src","kernel.jl")), "{connection_file}"]) - - - -kernelcmd = JSON.json(kernelcmd_array) - -add_config("ipython_config.py", "KernelManager.kernel_cmd", - kernelcmd, - overwrite=true) - -# make qtconsole require shift-enter to complete input -add_config("ipython_qtconsole_config.py", - "IPythonWidget.execute_on_complete_input", "False") - -add_config("ipython_qtconsole_config.py", - "FrontendWidget.lexer_class", "'pygments.lexers.JuliaLexer'") - -# set Julia notebook to use a different port than IPython's 8888 by default -add_config("ipython_notebook_config.py", "NotebookApp.port", 8998) - -####################################################################### -# Copying files into the correct paths in the profile lets us override -# the files of the same name in IPython. rb(filename::String) = open(readbytes, filename) eqb(a::Vector{Uint8}, b::Vector{Uint8}) = @@ -126,44 +40,18 @@ function copy_config(src::String, destpath::String, end end -# copy IJulia icon to profile so that IPython will use it -for T in ("png", "svg") - copy_config("ijulialogo.$T", - joinpath(juliaprof, "static", "base", "images"), - "ipynblogo.$T") -end - -# copy IJulia favicon to profile -copy_config("ijuliafavicon.ico", - joinpath(juliaprof, "static", "base", "images"), - "favicon.ico") - -# On IPython < 3. -# custom.js can contain custom js login that will be loaded -# with the notebook to add info and/or monkey-patch some javascript -# -- e.g. we use it to add .ipynb metadata that this is a Julia notebook - -# on IPython 3+, still upgrade custom.js because old version can prevent -# notebook from loading. -# todo: maybe do not copy if don't exist. -# todo: maybe remove if user custom.js is identical to the one -# shipped with IJulia ? -copy_config("custom.js", joinpath(juliaprof, "static", "custom")) - -# julia.js implements a CodeMirror mode for Julia syntax highlighting in the notebook. -# Eventually this will ship with CodeMirror and hence IPython, but for now we manually bundle it. - -copy_config("julia.js", joinpath(juliaprof, "static", "components", "codemirror", "mode", "julia")) - -####################################################################### -# Part specific to Jupyter/IPython 3.0 and above ####################################################################### +# Install IPython 3 kernel-spec file. -#Is IJulia being built from a debug build? If so, add "debug" to the description +# Is IJulia being built from a debug build? If so, add "debug" to the description. debugdesc = ccall(:jl_is_debugbuild,Cint,())==1 ? "-debug" : "" juliakspec = joinpath(chomp(readall(`$ipython locate`)), "kernels", "julia-$(VERSION.major).$(VERSION.minor)"*debugdesc) +binary_name = @windows? "julia.exe":"julia" +kernelcmd_array = [escape_string(joinpath(JULIA_HOME,("$binary_name"))), "-i"] +ijulia_dir = get(ENV, "IJULIA_DIR", Pkg.dir("IJulia")) # support non-Pkg IJulia installs +append!(kernelcmd_array, ["-F", escape_string(joinpath(ijulia_dir,"src","kernel.jl")), "{connection_file}"]) ks = @compat Dict( "argv" => kernelcmd_array, @@ -175,7 +63,6 @@ destname = "kernel.json" mkpath(juliakspec) dest = joinpath(juliakspec, destname) - eprintln("Writing IJulia kernelspec to $dest ...") open(dest, "w") do f diff --git a/deps/custom.js b/deps/custom.js deleted file mode 100644 index d1ea34c1..00000000 --- a/deps/custom.js +++ /dev/null @@ -1,471 +0,0 @@ -// leave at least 2 line with only a star on it below, or doc generation fails -/** - * - * - * Placeholder for custom user javascript - * mainly to be overridden in profile/static/js/custom.js - * This will always be an empty file in IPython - * - * User could add any javascript in the `profile/static/js/custom.js` file - * (and should create it if it does not exist). - * It will be executed by the ipython notebook at load time. - * - * Same thing with `profile/static/css/custom.css` to inject custom css into the notebook. - * - * Example : - * - * Create a custom button in toolbar that execute `%qtconsole` in kernel - * and hence open a qtconsole attached to the same kernel as the current notebook - * - * $([IPython.events]).on('notebook_loaded.Notebook', function(){ - * IPython.toolbar.add_buttons_group([ - * { - * 'label' : 'run qtconsole', - * 'icon' : 'ui-icon-calculator', // select your icon from http://jqueryui.com/themeroller/ - * 'callback': function(){IPython.notebook.kernel.execute('%qtconsole')} - * } - * // add more button here if needed. - * ]); - * }); - * - * Example : - * - * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );` - * to load custom script into the notebook. - * - * // to load the metadata ui extension example. - * $.getScript('/static/js/celltoolbarpresets/example.js'); - * // or - * // to load the metadata ui extension to control slideshow mode / reveal js for nbconvert - * $.getScript('/static/js/celltoolbarpresets/slideshow.js'); - * - * - * @module IPython - * @namespace IPython - * @class customjs - * @static - */ - -// end of IPython unmodified version - -/************************************/ -/** IPython 2 and earlier versions **/ -/************************************/ -// Note that custom.js shoudl not be needed anymore on IPython 3 and above -// we still get logics just to run nothing and avoid IPython not loading in the -// eventual presence of custom.js - -if (parseInt(IPython.version[0]) < 3) { - - - $([IPython.events]).on('notebook_loaded.Notebook', function(){ - "use strict"; - // add here logic that should be run once per **notebook load** - // (!= page load), like restarting a checkpoint - - var md = IPython.notebook.metadata; - if(md.language){ - console.log('language already defined and is :', md.language); - } else { - md.language = 'Julia' ; - console.log('add metadata hint that language is julia...'); - } - }); - - - $([IPython.events]).on('app_initialized.NotebookApp', function(){ - "use strict"; - // add here logic that should be run once per **page load** - // like adding specific UI for Julia, or changing the default value - // of codecell highlight to a julia one if availlable. - - // this will not work for 1.0, unless julia profile - // manually ships julia.js in - // /static/components/codemirror/mode/julia/julia.js - // hopefully it will be directly included in codemirror itself - // for future releases. - IPython.CodeCell.options_default['cm_config']['mode'] = 'julia'; - - CodeMirror.requireMode('julia', function(){ - var cells = IPython.notebook.get_cells(); - for(var i in cells){ - var c = cells[i]; - if (c.cell_type === 'code'){ - c.auto_highlight(); - } - } - }); - - // handle identifiers ending with ! (this works in ipython 2.x) - IPython.Tooltip.last_token_re = /[a-z_][0-9a-z._!]*$/gi; - }); - -} - - -/************************************/ -/** IPython 1 and earlier versions **/ -/************************************/ -// This is a copy of tooltip.js from 1.x of ipython. It will conflict with later -// ipython versions but is included here to support ! in identifier names for tooltips -if (parseInt(IPython.version[0]) <= 1) { - - 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; - - // display tooltip if the docstring is empty? - this._hide_if_no_docstring = false; - - // contain the button in the upper right corner - this.buttons = $('
').addClass('tooltipbuttons'); - - // will contain the docstring - this.text = $('
').addClass('tooltiptext').addClass('smalltooltip'); - - // build the buttons menu on the upper right - // expand the tooltip to see more - var expandlink = $('').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( - $('').text('Expand').addClass('ui-icon').addClass('ui-icon-plus')); - - // open in pager - var morelink = $('').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)'); - var morespan = $('').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n'); - morelink.append(morespan); - morelink.click(function () { - that.showInPager(that._old_cell); - }); - - // close the tooltip - var closelink = $('').attr('href', "#").attr('role', "button").addClass('ui-button'); - var closespan = $('').text('Close').addClass('ui-icon').addClass('ui-icon-close'); - closelink.append(closespan); - closelink.click(function () { - that.remove_and_cancel_tooltip(true); - }); - - this._clocklink = $('').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 = $('').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 = $('
').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); - }, function () { - that.expand(); - }, function () { - that.stick(); - }, function (cell) { - that.cancel_stick(); - that.showInPager(cell); - }]; - // 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 () {}; - cell.kernel.execute( - that.name + '?', { - 'execute_reply': empty, - 'output': empty, - 'clear_output': empty, - 'cell': cell - }, { - 'silent': false, - 'store_history': true - }); - this.remove_and_cancel_tooltip(); - } - - // grow the tooltip verticaly - Tooltip.prototype.expand = function () { - this.text.removeClass('smalltooltip'); - this.text.addClass('bigtooltip'); - $('#expanbutton').hide('slow'); - } - - // 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(); - } - - // 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, hide_if_no_docstring) { - var that = this; - this._tooltip_timeout = setTimeout(function () { - that.request(cell, hide_if_no_docstring) - }, 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 that = this - var callbacks_v2 = function(data){ - $.proxy(that._show(data.content), that) - } - var callbacks = { - 'object_info_reply': $.proxy(this._show, this) - } - if(cell.kernel.object_info_request){ - // we are on IPython 1.x and early 2.0 (before bidirectionnal js comm) - var msg_id = cell.kernel.object_info_request(re.exec(func), callbacks); - } else { - // we are after that, 2.0-dev late october and after - var msg_id = cell.kernel.object_info(re.exec(func), callbacks_v2); - } - } - - // make an imediate completion request - Tooltip.prototype.request = function (cell, hide_if_no_docstring) { - // 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(); - - this._hide_if_no_docstring = hide_if_no_docstring; - - if(editor.somethingSelected()){ - text = editor.getSelection(); - } - - // 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(); - - // whatever anchor/head order but arrow at mid x selection - var anchor = this.code_mirror.cursorCoords(false); - var head = this.code_mirror.cursorCoords(true); - var xinit = (head.left+anchor.left)/2; - 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': (head.bottom + 10) + 'px' - }); - } else { - this.tooltip.css({ - 'left': xinter - 30 + 'px' - }); - this.tooltip.css({ - 'top': (head.bottom + 10) + 'px' - }); - } - this.arrow.animate({ - 'left': posarrowleft + 'px' - }); - - // 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) { - // For reals this time, no docstring - if (this._hide_if_no_docstring) { - return; - } else { - docstring = ""; - } - } - - this.tooltip.fadeIn('fast'); - this._hidden = false; - this.text.children().remove(); - - var pre = $('
').html(utils.fixConsole(docstring));
-            if (defstring) {
-                var defstring_html = $('
').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);
-        }
-
-
-        IPython.Tooltip = Tooltip;
-
-        return IPython;
-
-    }(IPython));
-}
diff --git a/deps/julia.js b/deps/julia.js
deleted file mode 100644
index 3ef86d65..00000000
--- a/deps/julia.js
+++ /dev/null
@@ -1,292 +0,0 @@
-CodeMirror.defineMode("julia", function(_conf, parserConf) {
-  var ERRORCLASS = 'error';
-
-  function wordRegexp(words) {
-    return new RegExp("^((" + words.join(")|(") + "))\\b");
-  }
-
-  var operators = parserConf.operators || /^\.?[|&^\\%*+\-<>!=\/]=?|\?|~|:|\$|\.[<>]|<<=?|>>>?=?|\.[<>=]=|->?|\/\/|\bin\b/;
-  var delimiters = parserConf.delimiters || /^[;,()[\]{}]/;
-  var open_delimiters= /^[(\[{]/;
-  var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*!*/;
-  var blockOpeners = ["begin", "function", "type", "immutable", "let", "macro", "for", "while", "quote", "if", "else", "elseif", "try", "finally", "catch", "do"];
-  var blockClosers = ["end", "else", "elseif", "catch", "finally"];
-  var keywordList = ['if', 'else', 'elseif', 'while', 'for', 'begin', 'let', 'end', 'do', 'try', 'catch', 'finally', 'return', 'break', 'continue', 'global', 'local', 'const', 'export', 'import', 'importall', 'using', 'function', 'macro', 'module', 'baremodule', 'type', 'immutable', 'quote', 'typealias', 'abstract', 'bitstype', 'ccall'];
-  var builtinList = ['true', 'false', 'enumerate', 'open', 'close', 'nothing', 'NaN', 'Inf', 'print', 'println', 'Int', 'Int8', 'Uint8', 'Int16', 'Uint16', 'Int32', 'Uint32', 'Int64', 'Uint64', 'Int128', 'Uint128', 'Bool', 'Char', 'Float16', 'Float32', 'Float64', 'Array', 'Vector', 'Matrix', 'String', 'UTF8String', 'ASCIIString', 'error', 'warn', 'info', '@printf'];
-
-  //var stringPrefixes = new RegExp("^[br]?('|\")")
-  var stringPrefixes = /^(`|'|"{3}|([br]?"))/;
-  var keywords = wordRegexp(keywordList);
-  var builtins = wordRegexp(builtinList);
-  var openers = wordRegexp(blockOpeners);
-  var closers = wordRegexp(blockClosers);
-  var macro = /^@[_A-Za-z][_A-Za-z0-9]*/;
-  var codepointName = /^:[_A-Za-z][_A-Za-z0-9]*:/;
-  var symbol = /^:[_A-Za-z][_A-Za-z0-9]*/;
-  var indentInfo = null;
-
-  function in_array(state) {
-    var ch = cur_scope(state);
-    if(ch=="[" || ch=="{") {
-      return true;
-    }
-    else {
-      return false;
-    }
-  }
-
-  function cur_scope(state) {
-    if(state.scopes.length==0) {
-      return null;
-    }
-    return state.scopes[state.scopes.length - 1];
-  }
-
-  // tokenizers
-  function tokenBase(stream, state) {
-    // Handle scope changes
-    var leaving_expr = state.leaving_expr;
-    if(stream.sol()) {
-      leaving_expr = false;
-    }
-    state.leaving_expr = false;
-    if(leaving_expr) {
-      if(stream.match(/^'+/)) {
-        return 'operator';
-      }
-
-    }
-
-    if(stream.match(/^\.{2,3}/)) {
-      return 'operator';
-    }
-
-    if (stream.eatSpace()) {
-      return null;
-    }
-
-    var ch = stream.peek();
-    // Handle Comments
-    if (ch === '#') {
-        stream.skipToEnd();
-        return 'comment';
-    }
-    if(ch==='[') {
-      state.scopes.push("[");
-    }
-
-    if(ch==='{') {
-      state.scopes.push("{");
-    }
-
-    var scope=cur_scope(state);
-
-    if(scope==='[' && ch===']') {
-      state.scopes.pop();
-      state.leaving_expr=true;
-    }
-
-    if(scope==='{' && ch==='}') {
-      state.scopes.pop();
-      state.leaving_expr=true;
-    }
-
-    if(ch===')') {
-      state.leaving_expr = true;
-    }
-
-    var match;
-    if(!in_array(state) && (match=stream.match(openers, false))) {
-      state.scopes.push(match);
-    }
-
-    if(!in_array(state) && stream.match(closers, false)) {
-      state.scopes.pop();
-    }
-
-    if(in_array(state)) {
-      if(stream.match(/^end/)) {
-        return 'number';
-      }
-
-    }
-
-    if(stream.match(/^=>/)) {
-      return 'operator';
-    }
-
-
-    // Handle Number Literals
-    if (stream.match(/^[0-9\.]/, false)) {
-      var imMatcher = RegExp(/^im\b/);
-      var floatLiteral = false;
-      // Floats
-      if (stream.match(/^\d*\.(?!\.)\d+([ef][\+\-]?\d+)?/i)) { floatLiteral = true; }
-      if (stream.match(/^\d+\.(?!\.)\d*/)) { floatLiteral = true; }
-      if (stream.match(/^\.\d+/)) { floatLiteral = true; }
-      if (floatLiteral) {
-          // Float literals may be "imaginary"
-          stream.match(imMatcher);
-          state.leaving_expr = true;
-          return 'number';
-      }
-      // Integers
-      var intLiteral = false;
-      // Hex
-      if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
-      // Binary
-      if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
-      // Octal
-      if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
-      // Decimal
-      if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
-          intLiteral = true;
-      }
-      // Zero by itself with no other piece of number.
-      if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
-      if (intLiteral) {
-          // Integer literals may be "long"
-          stream.match(imMatcher);
-          state.leaving_expr = true;
-          return 'number';
-      }
-    }
-
-    if(stream.match(/^(::)|(<:)/)) {
-      return 'operator';
-    }
-
-    if(stream.match(codepointName)) {
-      return null;
-    }
-
-    // Handle symbols
-    if(!leaving_expr && stream.match(symbol)) {
-      return 'string';
-    }
-
-    // Handle operators and Delimiters
-    if (stream.match(operators)) {
-      return 'operator';
-    }
-
-
-    // Handle Strings
-    if (stream.match(stringPrefixes)) {
-      state.tokenize = tokenStringFactory(stream.current());
-      return state.tokenize(stream, state);
-    }
-
-    if (stream.match(macro)) {
-      return 'meta';
-    }
-
-
-    if (stream.match(delimiters)) {
-      return null;
-    }
-
-    if (stream.match(keywords)) {
-      return 'keyword';
-    }
-
-    if (stream.match(builtins)) {
-      return 'builtin';
-    }
-
-
-    if (stream.match(identifiers)) {
-      state.leaving_expr=true;
-      return 'variable';
-    }
-    // Handle non-detected items
-    stream.next();
-    return ERRORCLASS;
-  }
-
-  function tokenStringFactory(delimiter) {
-    while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
-      delimiter = delimiter.substr(1);
-    }
-    var singleline = delimiter.length == 1;
-    var OUTCLASS = 'string';
-
-    function tokenString(stream, state) {
-      while (!stream.eol()) {
-        stream.eatWhile(/[^'"\\]/);
-        if (stream.eat('\\')) {
-            stream.next();
-            if (singleline && stream.eol()) {
-              return OUTCLASS;
-            }
-        } else if (stream.match(delimiter)) {
-            state.tokenize = tokenBase;
-            return OUTCLASS;
-        } else {
-            stream.eat(/['"]/);
-        }
-      }
-      if (singleline) {
-        if (parserConf.singleLineStringErrors) {
-            return ERRORCLASS;
-        } else {
-            state.tokenize = tokenBase;
-        }
-      }
-      return OUTCLASS;
-    }
-    tokenString.isString = true;
-    return tokenString;
-  }
-
-  function tokenLexer(stream, state) {
-    indentInfo = null;
-    var style = state.tokenize(stream, state);
-    var current = stream.current();
-
-    // Handle '.' connected identifiers
-    if (current === '.') {
-      style = stream.match(identifiers, false) ? null : ERRORCLASS;
-      if (style === null && state.lastStyle === 'meta') {
-          // Apply 'meta' style to '.' connected identifiers when
-          // appropriate.
-        style = 'meta';
-      }
-      return style;
-    }
-
-    return style;
-  }
-
-  var external = {
-    startState: function() {
-      return {
-        tokenize: tokenBase,
-        scopes: [],
-        leaving_expr: false
-      };
-    },
-
-    token: function(stream, state) {
-      var style = tokenLexer(stream, state);
-      state.lastStyle = style;
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      var delta = 0;
-      if(textAfter=="end" || textAfter=="]" || textAfter=="}" || textAfter=="else" || textAfter=="elseif" || textAfter=="catch" || textAfter=="finally") {
-        delta = -1;
-      }
-      return (state.scopes.length + delta) * 4;
-    },
-
-    lineComment: "#",
-    fold: "indent",
-    electricChars: "edlsifyh]}"
-  };
-  return external;
-});
-
-
-CodeMirror.defineMIME("text/x-julia", "julia");
diff --git a/src/IJulia.jl b/src/IJulia.jl
index 50eb95b8..a5c60a4c 100644
--- a/src/IJulia.jl
+++ b/src/IJulia.jl
@@ -15,12 +15,6 @@ using JSON
 using Nettle
 include("../deps/ipython.jl")
 
-if isdefined(Base, :REPLCompletions)
-    using Base.REPLCompletions
-else
-    using REPLCompletions
-end
-
 uuid4() = repr(Base.Random.uuid4())
 
 inited = false
@@ -127,17 +121,18 @@ function eventloop(socket)
                 # (Ignore SIGINT since this may just be a user-requested
                 #  kernel interruption to interrupt long calculations.)
                 if !isa(e, InterruptException)
-                    content = pyerr_content(e, "KERNEL EXCEPTION")
+                    content = error_content(e, msg="KERNEL EXCEPTION")
                     map(s -> println(orig_STDERR, s), content["traceback"])
                     send_ipython(publish, 
                                  execute_msg == nothing ?
-                                 Msg([ "pyerr" ],
+                                 Msg([ "error" ],
                                      @compat(Dict("msg_id" => uuid4(),
                                                   "username" => "jlkernel",
                                                   "session" => uuid4(),
-                                                  "msg_type" => "pyerr")),
+                                                  "msg_type" => "error",
+                                                  "version" => "5.0")),
                                      content) :
-                                 msg_pub(execute_msg, "pyerr", content)) 
+                                 msg_pub(execute_msg, "error", content)) 
                 end
             finally
                 send_status("idle", msg.header)
diff --git a/src/execute_request.jl b/src/execute_request.jl
index ac89ef80..2b1d9d08 100644
--- a/src/execute_request.jl
+++ b/src/execute_request.jl
@@ -11,6 +11,7 @@ const text_plain = MIME("text/plain")
 const image_svg = MIME("image/svg+xml")
 const image_png = MIME("image/png")
 const image_jpeg = MIME("image/jpeg")
+const text_markdown = MIME("text/markdown")
 const text_html = MIME("text/html")
 const text_latex = MIME("text/latex") # IPython expects this
 const text_latex2 = MIME("application/x-latex") # but this is more standard?
@@ -35,6 +36,9 @@ function display_dict(x)
     if mimewritable(text_html, x)
         data[string(text_html)] = stringmime(text_html, x)
     end
+    if mimewritable(text_markdown, x)
+        data[string(text_markdown)] = stringmime(text_markdown, x)
+    end
     if mimewritable(text_latex, x)
         data[string(text_latex)] = stringmime(text_latex, x)
     elseif mimewritable(text_latex2, x)
@@ -58,10 +62,10 @@ end
 #######################################################################
 
 # return the content of a pyerr message for exception e
-function pyerr_content(e, msg::AbstractString="")
+function error_content(e; backtrace_top::Symbol=:execute_request_0x535c5df2, msg::AbstractString="")
     bt = catch_backtrace()
     tb = map(utf8, @compat(split(sprint(Base.show_backtrace, 
-                                        :execute_request_0x535c5df2, 
+                                        backtrace_top, 
                                         bt, 1:typemax(Int)),
                                  "\n", keep=true)))
     if !isempty(tb) && ismatch(r"^\s*in\s+include_string\s+", tb[end])
@@ -78,8 +82,7 @@ function pyerr_content(e, msg::AbstractString="")
     if !isempty(msg)
         unshift!(tb, msg)
     end
-    @compat Dict("execution_count" => _n,
-                 "ename" => ename, "evalue" => evalue,
+    @compat Dict("ename" => ename, "evalue" => evalue,
                  "traceback" => tb)
 end
 
@@ -113,6 +116,17 @@ if VERSION >= v"0.4.0-dev+1853"
     Base.repl_cmd(cmd) = Base.repl_cmd(cmd, STDOUT)
 end
 
+function helpcode(code::AbstractString)
+    if VERSION < v"0.4.0-dev+2891" # old Base.@help macro
+        return "Base.@help " * code
+    else # new Base.Docs.@repl macro from julia@08663d4bb05c5b8805a57f46f4feacb07c7f2564
+        code_ = strip(code)
+        # as in base/REPL.jl, special-case keywords so that they parse
+        return "Base.Docs.@repl " * (haskey(Docs.keywords, symbol(code_)) ?
+                                     ":"*code_ : code_)
+    end
+end
+
 # note: 0x535c5df2 is a random integer to make name collisions in
 # backtrace analysis less likely.
 function execute_request_0x535c5df2(socket, msg)
@@ -120,19 +134,21 @@ function execute_request_0x535c5df2(socket, msg)
     @vprintln("EXECUTING ", code)
     global execute_msg = msg
     global _n, In, Out, ans
-    silent = msg.content["silent"] || ismatch(r";\s*$", code)
-
-    # present in spec but missing from notebook's messages:
+    silent = msg.content["silent"]
     store_history = get(msg.content, "store_history", !silent)
 
-    _n += 1
+    if !silent
+        _n += 1
+        send_ipython(publish, 
+                     msg_pub(msg, "execute_input",
+                             @compat Dict("execution_count" => _n,
+                                          "code" => code)))
+    end
+    
+    silent = silent || ismatch(r";\s*$", code)
     if store_history
         In[_n] = code
     end
-    send_ipython(publish, 
-                 msg_pub(msg, "pyin",
-                         @compat Dict("execution_count" => _n,
-                                      "code" => code)))
 
     # "; ..." cells are interpreted as shell commands for run
     code = replace(code, r"^\s*;.*$", 
@@ -140,16 +156,9 @@ function execute_request_0x535c5df2(socket, msg)
                                "`)"), 0)
 
     # a cell beginning with "? ..." is interpreted as a help request
-    helpcode = replace(code, r"^\s*\?", "")
-    if helpcode != code
-        if VERSION < v"0.4.0-dev+2891" # old Base.@help macro
-            code = "Base.@help " * helpcode
-        else # new Base.Docs.@repl macro from julia@08663d4bb05c5b8805a57f46f4feacb07c7f2564
-            code = strip(helpcode)
-            # as in base/REPL.jl, special-case keywords so that they parse
-            code = "Base.Docs.@repl " * (haskey(Docs.keywords, symbol(code)) ?
-                                         ":"*code : code)
-        end
+    hcode = replace(code, r"^\s*\?", "")
+    if hcode != code
+        code = helpcode(hcode)
     end
 
     try 
@@ -167,11 +176,7 @@ function execute_request_0x535c5df2(socket, msg)
             end
         end
 
-        user_variables = Dict()
         user_expressions = Dict()
-        for v in get(msg.content, "user_variables", AbstractString[]) # gone in IPy3
-            user_variables[v] = eval(Main,parse(v))
-        end
         for (v,ex) in msg.content["user_expressions"]
             user_expressions[v] = eval(Main,parse(ex))
         end
@@ -196,7 +201,7 @@ function execute_request_0x535c5df2(socket, msg)
             result_metadata = invoke(metadata, (typeof(result),), result)
 
             send_ipython(publish,
-                         msg_pub(msg, "pyout",
+                         msg_pub(msg, "execute_result",
                                  @compat Dict("execution_count" => _n,
                                               "metadata" => result_metadata,
                                               "data" => display_dict(result))))
@@ -211,8 +216,6 @@ function execute_request_0x535c5df2(socket, msg)
                      msg_reply(msg, "execute_reply",
                                @compat Dict("status" => "ok",
                                             "execution_count" => _n,
-                                            "payload" => [],
-                                            "user_variables" => user_variables,
                                             "user_expressions" => user_expressions)))
     catch e
         try
@@ -227,8 +230,8 @@ function execute_request_0x535c5df2(socket, msg)
         catch
         end
         empty!(displayqueue) # discard pending display requests on an error
-        content = pyerr_content(e)
-        send_ipython(publish, msg_pub(msg, "pyerr", content))
+        content = error_content(e)
+        send_ipython(publish, msg_pub(msg, "error", content))
         content["status"] = "error"
         send_ipython(requests, msg_reply(msg, "execute_reply", content))
     end
diff --git a/src/handlers.jl b/src/handlers.jl
index 4e5303fc..c8e8564f 100644
--- a/src/handlers.jl
+++ b/src/handlers.jl
@@ -4,29 +4,41 @@ include("execute_request.jl")
 using IJulia.CommManager
 
 function complete_request(socket, msg)
-    text = msg.content["text"]
-    line = msg.content["line"]
-    cursorpos = chr2ind(line, msg.content["cursor_pos"])
+    code = msg.content["code"]
+    cursorpos = chr2ind(code, msg.content["cursor_pos"])
 
-    comps, positions = completions(line,cursorpos)
-    if sizeof(text) > length(positions)
-        comps = [line[(cursorpos-sizeof(text)+1):(cursorpos-length(positions))]*s for s in comps]
-    end
+    comps, positions = Base.REPLCompletions.completions(code, cursorpos)
     send_ipython(requests, msg_reply(msg, "complete_reply", 
                                      @compat Dict("status" => "ok",
                                                   "matches" => comps,
-                                                  "matched_text" => 
-                                                     line[positions])))
+                                                  "cursor_start" => ind2chr(code,first(positions))-1,
+                                                  "cursor_end" => ind2chr(code,last(positions)),
+                                                  "status"=>"ok")))
 end
 
 function kernel_info_request(socket, msg)
     send_ipython(requests,
                  msg_reply(msg, "kernel_info_reply",
-                           @compat Dict("protocol_version" => [4, 0],
-                                        "language_version" => [VERSION.major,
-                                                               VERSION.minor,
-                                                               VERSION.patch],
-                                        "language" => "julia")))
+                           @compat Dict("protocol_version" => "5.0",
+                                        "implementation" => "ijulia",
+                                        # TODO: "implementation_version" => IJulia version string from Pkg
+                                        "language_info" =>
+                                        @compat(Dict("name" => "julia",
+                                                     "version" =>
+                                                     string(VERSION.major, '.',
+                                                            VERSION.minor, '.',
+                                                            VERSION.patch),
+                                                     "mimetype" => "application/julia",
+                                                     "file_extension" => ".jl")),
+                                        "banner" => "Julia: A fresh approach to technical computing.",
+                                        "help_links" => [
+                                                         @compat(Dict("text"=>"Julia Home Page",
+                                                                      "url"=>"http://julialang.org/")),
+                                                         @compat(Dict("text"=>"Julia Documentation",
+                                                                      "url"=>"http://docs.julialang.org/")),
+                                                         @compat(Dict("text"=>"Julia Packages",
+                                                                      "url"=>"http://pkg.julialang.org/"))
+                                                        ])))
 end
 
 function connect_request(socket, msg)
@@ -41,43 +53,72 @@ end
 function shutdown_request(socket, msg)
     send_ipython(requests, msg_reply(msg, "shutdown_reply",
                                      msg.content))
+    sleep(0.1) # short delay (like in ipykernel), to hopefully ensure shutdown_reply is sent
     exit()
 end
 
 # TODO: better Julia help integration (issue #13)
-docstring(o) = ""
-docstring(o::Union(Function,DataType)) = sprint(show, methods(o))
-
-function docstring(s::AbstractString, o)
+docdict(o) = @compat Dict()
+docdict(o::Union(Function,DataType)) = display_dict(methods(o))
+function docdict(s::AbstractString, o)
     d = sprint(help, s)
-    println(orig_STDOUT, s, " => ", d)
-    return startswith(d, "Symbol not found.") ? docstring(o) : d
+    return startswith(d, "Symbol not found.") ? docdict(o) : @compat Dict("text/plain" => d)
+end
+
+import Base: is_id_char, is_id_start_char
+function get_token(code, pos)
+    # given a string and a cursor position, find substring to request
+    # help on by:
+    #   1) searching backwards, skipping invalid identifier chars
+    #        ... search forward for end of identifier
+    #   2) search backwards to find the biggest identifier (including .)
+    #   3) if nothing found, do return empty string
+    # TODO: detect operators?
+
+    startpos = pos
+    while startpos > start(code)
+        if is_id_char(code[startpos])
+            break
+        else
+            startpos = prevind(code, startpos)
+        end
+    end
+    endpos = startpos
+    while startpos >= start(code) && (is_id_char(code[startpos]) || code[startpos] == '.')
+        startpos = prevind(code, startpos)
+    end
+    startpos = startpos < pos ? nextind(code, startpos) : pos
+    if !is_id_start_char(code[startpos])
+        return ""
+    end
+    while endpos < endof(code) && is_id_char(code[endpos])
+        endpos = nextind(code, endpos)
+    end
+    if !is_id_char(code[endpos])
+        endpos = prevind(code, endpos)
+    end
+    return code[startpos:endpos]
 end
 
-function object_info_request(socket, msg)
+function inspect_request_0x535c5df2(socket, msg)
     try
-        s = msg.content["oname"]
-        o = eval(Main, parse(s))
-        content = @compat Dict("oname" => msg.content["oname"],
-                               "found" => true,
-                               "ismagic" => false,
-                               "isalias" => false,
-                               "docstring" => docstring(s, o),
-                               "type_name" => string(typeof(o)),
-                               "base_class" => string(super(typeof(o))),
-                               "string_form" => 
-                                   get(msg.content,"detail_level",0) == 0 ? 
-                                       sprint(16384, show, o) : repr(o))
-        if method_exists(length, @compat Tuple{typeof(o)})
-            content["length"] = length(o)
+        code = msg.content["code"]
+        s = get_token(code, chr2ind(code, msg.content["cursor_pos"]))
+
+        if isempty(s)
+            content = @compat Dict("status" => "ok", "found" => false)
+        else
+            d = docdict(s, eval(Main, parse(s)))
+            content = @compat Dict("status" => "ok",
+                                   "found" => !isempty(d),
+                                   "data" => d)
         end
-        send_ipython(requests, msg_reply(msg, "object_info_reply", content))
+        send_ipython(requests, msg_reply(msg, "inspect_reply", content))
     catch e
-        @verror_show e catch_backtrace()
+        content = error_content(e, backtrace_top=:inspect_request_0x535c5df2);
+        content["status"] = "error"
         send_ipython(requests,
-                     msg_reply(msg, "object_info_reply",
-                               @compat Dict("oname" => msg.content["oname"],
-                                            "found" => false)))
+                     msg_reply(msg, "inspect_reply", content))
     end
 end
 
@@ -94,7 +135,7 @@ const handlers = @compat(Dict{AbstractString,Function}(
     "execute_request" => execute_request_0x535c5df2,
     "complete_request" => complete_request,
     "kernel_info_request" => kernel_info_request,
-    "object_info_request" => object_info_request,
+    "inspect_request" => inspect_request_0x535c5df2,
     "connect_request" => connect_request,
     "shutdown_request" => shutdown_request,
     "history_request" => history_request,
diff --git a/src/msg.jl b/src/msg.jl
index 2c29ccef..34049986 100644
--- a/src/msg.jl
+++ b/src/msg.jl
@@ -25,7 +25,8 @@ msg_pub(m::Msg, msg_type, content, metadata=Dict{AbstractString,Any}()) =
       @compat(Dict("msg_id" => uuid4(),
                    "username" => m.header["username"],
                    "session" => m.header["session"],
-                   "msg_type" => msg_type)),
+                   "msg_type" => msg_type,
+                   "version" => "5.0")),
       content, m.header, metadata)
 
 msg_reply(m::Msg, msg_type, content, metadata=Dict{AbstractString,Any}()) =
@@ -33,7 +34,8 @@ msg_reply(m::Msg, msg_type, content, metadata=Dict{AbstractString,Any}()) =
       @compat(Dict("msg_id" => uuid4(),
                    "username" => m.header["username"],
                    "session" => m.header["session"],
-                   "msg_type" => msg_type)),
+                   "msg_type" => msg_type,
+                   "version" => "5.0")),
       content, m.header, metadata)
 
 function show(io::IO, msg::Msg)
@@ -91,7 +93,8 @@ function send_status(state::AbstractString, parent_header=nothing)
                   @compat(Dict("msg_id" => uuid4(),
                                "username" => "jlkernel",
                                "session" => execute_msg.header["session"],
-                               "msg_type" => "status")),
+                               "msg_type" => "status",
+                               "version" => "5.0")),
                   @compat(Dict("execution_state" => state))
         )
     else
@@ -100,7 +103,8 @@ function send_status(state::AbstractString, parent_header=nothing)
                   @compat(Dict("msg_id" => uuid4(),
                                "username" => "jlkernel",
                                "session" => execute_msg.header["session"],
-                               "msg_type" => "status")),
+                               "msg_type" => "status",
+                               "version" => "5.0")),
                   @compat(Dict("execution_state" => state)),
                   parent_header
         )
diff --git a/src/stdio.jl b/src/stdio.jl
index 9418d3cf..d47691b4 100644
--- a/src/stdio.jl
+++ b/src/stdio.jl
@@ -31,7 +31,7 @@ function send_stream(rd::IO, name::AbstractString)
         end
         send_ipython(publish,
                      msg_pub(execute_msg, "stream",
-                             @compat Dict("name" => name, "data" => s)))
+                             @compat Dict("name" => name, "text" => s)))
     end
 end
 
@@ -64,7 +64,7 @@ function readline(io::Base.Pipe)
         end
         send_ipython(raw_input,
                      msg_reply(execute_msg, "input_request",
-                               @compat Dict("prompt" => "STDIN> ")))
+                               @compat Dict("prompt"=>"STDIN> ", "password"=>false)))
         while true
             msg = recv_ipython(raw_input)
             if msg.header["msg_type"] == "input_reply"