diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index b8aba88..e2b532e 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -1,13 +1,10 @@ -//---------------------------------------------------------------------------- -// Copyright (C) 2008-2012 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. -//---------------------------------------------------------------------------- +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. //============================================================================ // Utilities //============================================================================ + IPython.namespace('IPython.utils'); IPython.utils = (function (IPython) { @@ -430,7 +427,7 @@ IPython.utils = (function (IPython) { var escape_html = function (text) { // escape text to HTML return $("
").text(text).html(); - } + }; var get_body_data = function(key) { @@ -439,8 +436,19 @@ IPython.utils = (function (IPython) { // until we are building an actual request return decodeURIComponent($('body').data(key)); }; - - + + var absolute_cursor_pos = function (cm, cursor) { + // get the absolute cursor position from CodeMirror's col, ch + if (!cursor) { + cursor = cm.getCursor(); + } + var cursor_pos = cursor.ch; + for (var i = 0; i < cursor.line; i++) { + cursor_pos += cm.getLine(i).length + 1; + } + return cursor_pos; + }; + // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript var browser = (function() { if (typeof navigator === 'undefined') { @@ -465,13 +473,13 @@ IPython.utils = (function (IPython) { if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS"; if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX"; if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux"; - return OSName + return OSName; })(); var is_or_has = function (a, b) { // Is b a child of a or a itself? return a.has(b).length !==0 || a.is(b); - } + }; var is_focused = function (e) { // Is element e, or one of its children focused? @@ -486,7 +494,7 @@ IPython.utils = (function (IPython) { } else { return false; } - } + }; var log_ajax_error = function (jqXHR, status, error) { // log ajax failures with informative messages @@ -499,7 +507,7 @@ IPython.utils = (function (IPython) { } console.log(msg); }; - + return { regex_split : regex_split, uuid : uuid, @@ -515,6 +523,7 @@ IPython.utils = (function (IPython) { splitext : splitext, escape_html : escape_html, always_new : always_new, + absolute_cursor_pos : absolute_cursor_pos, browser : browser, platform: platform, is_or_has : is_or_has, diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index df940b3..ac6d5f1 100644 --- a/IPython/html/static/notebook/js/completer.js +++ b/IPython/html/static/notebook/js/completer.js @@ -1,6 +1,10 @@ -// function completer. +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +// Completer // -// completer should be a class that takes an cell instance +// Completer is be a class that takes a cell instance. + var IPython = (function (IPython) { // that will prevent us from misspelling "use strict"; @@ -145,11 +149,14 @@ var IPython = (function (IPython) { // we fork here and directly call finish completing if kernel is busy if (this.skip_kernel_completion) { this.finish_completing({ - 'matches': [], + matches: [], matched_text: "" }); } else { - this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this)); + var cursor_pos = IPython.utils.absolute_cursor_pos(this.editor, cur); + this.cell.kernel.complete(this.editor.getValue(), cursor_pos, + $.proxy(this.finish_completing, this) + ); } }; diff --git a/IPython/html/static/notebook/js/tooltip.js b/IPython/html/static/notebook/js/tooltip.js index 7c55d6f..63da7f3 100644 --- a/IPython/html/static/notebook/js/tooltip.js +++ b/IPython/html/static/notebook/js/tooltip.js @@ -1,9 +1,6 @@ -//---------------------------------------------------------------------------- -// 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. -//---------------------------------------------------------------------------- +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + //============================================================================ // Tooltip //============================================================================ @@ -105,8 +102,8 @@ var IPython = (function (IPython) { 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); + this.tabs_functions = [function (cell, text, cursor) { + that._request_tooltip(cell, text, cursor); }, function () { that.expand(); }, function () { @@ -131,13 +128,10 @@ var IPython = (function (IPython) { Tooltip.prototype.showInPager = function (cell) { // reexecute last call in pager by appending ? to show back in pager var that = this; - var callbacks = {'shell' : { - 'payload' : { - 'page' : $.proxy(cell._open_with_pager, cell) - } - } - }; - cell.kernel.execute(that.name + '?', callbacks, {'silent': false, 'store_history': true}); + var payload = {}; + payload.text = that._reply.content.data['text/plain']; + + $([IPython.events]).trigger('open_with_text.Pager', payload); this.remove_and_cancel_tooltip(); }; @@ -222,10 +216,9 @@ var IPython = (function (IPython) { return Tooltip.last_token_re.exec(line); }; - Tooltip.prototype._request_tooltip = function (cell, line) { + Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) { var callbacks = $.proxy(this._show, this); - var oir_token = this.extract_oir_token(line); - var msg_id = cell.kernel.object_info(oir_token, callbacks); + var msg_id = cell.kernel.object_info(text, cursor_pos, callbacks); }; // make an imediate completion request @@ -236,10 +229,8 @@ var IPython = (function (IPython) { this.cancel_pending(); var editor = cell.code_mirror; var cursor = editor.getCursor(); - var text = editor.getRange({ - line: cursor.line, - ch: 0 - }, cursor).trim(); + var cursor_pos = IPython.utils.absolute_cursor_pos(editor, cursor); + var text = cell.get_text(); this._hide_if_no_docstring = hide_if_no_docstring; @@ -260,17 +251,17 @@ var IPython = (function (IPython) { this.reset_tabs_function (cell, text); } - // don't do anything if line beggin with '(' or is empty - if (text === "" || text === "(") { - return; - } + // don't do anything if line begins with '(' or is empty + // if (text === "" || text === "(") { + // return; + // } - this.tabs_functions[this._consecutive_counter](cell, text); + this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos); // 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); - } + this.reset_tabs_function (cell, text, cursor); + } return; }; @@ -302,6 +293,7 @@ var IPython = (function (IPython) { Tooltip.prototype._show = function (reply) { // move the bubble if it is not hidden // otherwise fade it + this._reply = reply; var content = reply.content; if (!content.found) { // object not found, nothing to show @@ -338,43 +330,14 @@ var IPython = (function (IPython) { this.arrow.animate({ 'left': posarrowleft + 'px' }); - - // build docstring - var defstring = content.call_def; - if (!defstring) { - defstring = content.init_definition; - } - if (!defstring) { - defstring = content.definition; - } - - var docstring = content.call_docstring; - if (!docstring) { - docstring = content.init_docstring; - } - if (!docstring) { - docstring = content.docstring; - } - - if (!docstring) { - // For reals this time, no docstring - if (this._hide_if_no_docstring) { - return; - } else { - docstring = ""; - } - } - + this._hidden = false; this.tooltip.fadeIn('fast'); this.text.children().remove(); - + + // This should support rich data types, but only text/plain for now // Any HTML within the docstring is escaped by the fixConsole() method. - var pre = $('
').html(utils.fixConsole(docstring));
-        if (defstring) {
-            var defstring_html = $('
').html(utils.fixConsole(defstring));
-            this.text.append(defstring_html);
-        }
+        var pre = $('
').html(utils.fixConsole(content.data['text/plain']));
         this.text.append(pre);
         // keep scroll top to be sure to always see the first line
         this.text.scrollTop(0);
diff --git a/IPython/html/static/services/kernels/js/kernel.js b/IPython/html/static/services/kernels/js/kernel.js
index a7a5b56..75c026e 100644
--- a/IPython/html/static/services/kernels/js/kernel.js
+++ b/IPython/html/static/services/kernels/js/kernel.js
@@ -263,7 +263,8 @@ var IPython = (function (IPython) {
     /**
      * Get info on an object
      *
-     * @param objname {string}
+     * @param code {string}
+     * @param cursor_pos {integer}
      * @param callback {function}
      * @method object_info
      *
@@ -271,20 +272,18 @@ var IPython = (function (IPython) {
      * The callback will be passed the complete `object_info_reply` message documented
      * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
      */
-    Kernel.prototype.object_info = function (objname, callback) {
+    Kernel.prototype.object_info = function (code, cursor_pos, callback) {
         var callbacks;
         if (callback) {
             callbacks = { shell : { reply : callback } };
         }
         
-        if (typeof(objname) !== null && objname !== null) {
-            var content = {
-                oname : objname.toString(),
-                detail_level : 0,
-            };
-            return this.send_shell_message("object_info_request", content, callbacks);
-        }
-        return;
+        var content = {
+            code : code,
+            cursor_pos : cursor_pos,
+            detail_level : 0,
+        };
+        return this.send_shell_message("object_info_request", content, callbacks);
     };
 
     /**
@@ -360,21 +359,19 @@ var IPython = (function (IPython) {
      * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
      *
      * @method complete
-     * @param line {integer}
+     * @param code {string}
      * @param cursor_pos {integer}
      * @param callback {function}
      *
      */
-    Kernel.prototype.complete = function (line, cursor_pos, callback) {
+    Kernel.prototype.complete = function (code, cursor_pos, callback) {
         var callbacks;
         if (callback) {
             callbacks = { shell : { reply : callback } };
         }
         var content = {
-            text : '',
-            line : line,
-            block : null,
-            cursor_pos : cursor_pos
+            code : code,
+            cursor_pos : cursor_pos,
         };
         return this.send_shell_message("complete_request", content, callbacks);
     };
diff --git a/IPython/kernel/channels.py b/IPython/kernel/channels.py
index a85a0ef..a0da872 100644
--- a/IPython/kernel/channels.py
+++ b/IPython/kernel/channels.py
@@ -270,38 +270,38 @@ class ShellChannel(ZMQSocketChannel):
         self._queue_send(msg)
         return msg['header']['msg_id']
 
-    def complete(self, text, line, cursor_pos, block=None):
+    def complete(self, code, cursor_pos=0, block=None):
         """Tab complete text in the kernel's namespace.
 
         Parameters
         ----------
-        text : str
-            The text to complete.
-        line : str
-            The full line of text that is the surrounding context for the
-            text to complete.
-        cursor_pos : int
-            The position of the cursor in the line where the completion was
-            requested.
-        block : str, optional
-            The full block of code in which the completion is being requested.
+        code : str
+            The context in which completion is requested.
+            Can be anything between a variable name and an entire cell.
+        cursor_pos : int, optional
+            The position of the cursor in the block of code where the completion was requested.
 
         Returns
         -------
         The msg_id of the message sent.
         """
-        content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
+        content = dict(code=code, cursor_pos=cursor_pos)
         msg = self.session.msg('complete_request', content)
         self._queue_send(msg)
         return msg['header']['msg_id']
 
-    def object_info(self, oname, detail_level=0):
+    def object_info(self, code, cursor_pos=0, detail_level=0):
         """Get metadata information about an object in the kernel's namespace.
 
+        It is up to the kernel to determine the appropriate object to inspect.
+
         Parameters
         ----------
-        oname : str
-            A string specifying the object name.
+        code : str
+            The context in which info is requested.
+            Can be anything between a variable name and an entire cell.
+        cursor_pos : int, optional
+            The position of the cursor in the block of code where the info was requested.
         detail_level : int, optional
             The level of detail for the introspection (0-2)
 
@@ -309,7 +309,9 @@ class ShellChannel(ZMQSocketChannel):
         -------
         The msg_id of the message sent.
         """
-        content = dict(oname=oname, detail_level=detail_level)
+        content = dict(code=code, cursor_pos=cursor_pos,
+            detail_level=detail_level,
+        )
         msg = self.session.msg('object_info_request', content)
         self._queue_send(msg)
         return msg['header']['msg_id']
diff --git a/IPython/kernel/inprocess/channels.py b/IPython/kernel/inprocess/channels.py
index cad01d2..15106d0 100644
--- a/IPython/kernel/inprocess/channels.py
+++ b/IPython/kernel/inprocess/channels.py
@@ -94,14 +94,16 @@ class InProcessShellChannel(InProcessChannel):
         self._dispatch_to_kernel(msg)
         return msg['header']['msg_id']
 
-    def complete(self, text, line, cursor_pos, block=None):
-        content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
+    def complete(self, code, cursor_pos=0):
+        content = dict(code=code, cursor_pos=cursor_pos)
         msg = self.client.session.msg('complete_request', content)
         self._dispatch_to_kernel(msg)
         return msg['header']['msg_id']
 
-    def object_info(self, oname, detail_level=0):
-        content = dict(oname=oname, detail_level=detail_level)
+    def object_info(self, code, cursor_pos=0, detail_level=0):
+        content = dict(code=code, cursor_pos=cursor_pos,
+            detail_level=detail_level,
+        )
         msg = self.client.session.msg('object_info_request', content)
         self._dispatch_to_kernel(msg)
         return msg['header']['msg_id']
diff --git a/IPython/kernel/inprocess/tests/test_kernelmanager.py b/IPython/kernel/inprocess/tests/test_kernelmanager.py
index ec07d07..4614bf3 100644
--- a/IPython/kernel/inprocess/tests/test_kernelmanager.py
+++ b/IPython/kernel/inprocess/tests/test_kernelmanager.py
@@ -1,19 +1,10 @@
-#-------------------------------------------------------------------------------
-#  Copyright (C) 2012  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.
-#-------------------------------------------------------------------------------
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
 
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
 from __future__ import print_function
 
-# Standard library imports
 import unittest
 
-# Local imports
 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
 from IPython.kernel.inprocess.manager import InProcessKernelManager
 
@@ -71,7 +62,7 @@ class InProcessKernelManagerTestCase(unittest.TestCase):
         kc = BlockingInProcessKernelClient(kernel=km.kernel)
         kc.start_channels()
         km.kernel.shell.push({'my_bar': 0, 'my_baz': 1})
-        kc.complete('my_ba', 'my_ba', 5)
+        kc.complete('my_ba', 5)
         msg = kc.get_shell_msg()
         self.assertEqual(msg['header']['msg_type'], 'complete_reply')
         self.assertEqual(sorted(msg['content']['matches']),
@@ -87,9 +78,12 @@ class InProcessKernelManagerTestCase(unittest.TestCase):
         km.kernel.shell.user_ns['foo'] = 1
         kc.object_info('foo')
         msg = kc.get_shell_msg()
-        self.assertEquals(msg['header']['msg_type'], 'object_info_reply')
-        self.assertEquals(msg['content']['name'], 'foo')
-        self.assertEquals(msg['content']['type_name'], 'int')
+        self.assertEqual(msg['header']['msg_type'], 'object_info_reply')
+        content = msg['content']
+        assert content['found']
+        self.assertEqual(content['name'], 'foo')
+        text = content['data']['text/plain']
+        self.assertIn('int', text)
 
     def test_history(self):
         """ Does requesting history from an in-process kernel work?
diff --git a/IPython/kernel/tests/test_message_spec.py b/IPython/kernel/tests/test_message_spec.py
index 4664fbf..17eecf4 100644
--- a/IPython/kernel/tests/test_message_spec.py
+++ b/IPython/kernel/tests/test_message_spec.py
@@ -85,6 +85,17 @@ class RHeader(Reference):
     username = Unicode()
     version = Version('5.0')
 
+mime_pat = re.compile(r'\w+/\w+')
+
+class MimeBundle(Reference):
+    metadata = Dict()
+    data = Dict()
+    def _data_changed(self, name, old, new):
+        for k,v in iteritems(new):
+            assert mime_pat.match(k)
+            nt.assert_is_instance(v, string_types)
+
+# shell replies
 
 class ExecuteReply(Reference):
     execution_count = Integer()
@@ -109,31 +120,9 @@ class ExecuteReplyError(Reference):
     traceback = List(Unicode)
 
 
-class OInfoReply(Reference):
+class OInfoReply(MimeBundle):
     name = Unicode()
     found = Bool()
-    ismagic = Bool()
-    isalias = Bool()
-    namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
-    type_name = Unicode()
-    string_form = Unicode()
-    base_class = Unicode()
-    length = Integer()
-    file = Unicode()
-    definition = Unicode()
-    argspec = Dict()
-    init_definition = Unicode()
-    docstring = Unicode()
-    init_docstring = Unicode()
-    class_docstring = Unicode()
-    call_def = Unicode()
-    call_docstring = Unicode()
-    source = Unicode()
-    
-    def check(self, d):
-        super(OInfoReply, self).check(d)
-        if d['argspec'] is not None:
-            ArgSpec().check(d['argspec'])
 
 
 class ArgSpec(Reference):
@@ -173,25 +162,12 @@ class Stream(Reference):
     data = Unicode()
 
 
-mime_pat = re.compile(r'\w+/\w+')
-
-class DisplayData(Reference):
+class DisplayData(MimeBundle):
     source = Unicode()
-    metadata = Dict()
-    data = Dict()
-    def _data_changed(self, name, old, new):
-        for k,v in iteritems(new):
-            assert mime_pat.match(k)
-            nt.assert_is_instance(v, string_types)
 
 
-class ExecuteResult(Reference):
+class ExecuteResult(MimeBundle):
     execution_count = Integer()
-    data = Dict()
-    def _data_changed(self, name, old, new):
-        for k,v in iteritems(new):
-            assert mime_pat.match(k)
-            nt.assert_is_instance(v, string_types)
 
 
 references = {
@@ -334,8 +310,10 @@ def test_oinfo_found():
     validate_message(reply, 'object_info_reply', msg_id)
     content = reply['content']
     assert content['found']
-    argspec = content['argspec']
-    nt.assert_is(argspec, None)
+    nt.assert_equal(content['name'], 'a')
+    text = content['data']['text/plain']
+    nt.assert_in('Type:', text)
+    nt.assert_in('Docstring:', text)
 
 
 def test_oinfo_detail():
@@ -343,14 +321,15 @@ def test_oinfo_detail():
 
     msg_id, reply = execute(code='ip=get_ipython()')
     
-    msg_id = KC.object_info('ip.object_inspect', detail_level=2)
+    msg_id = KC.object_info('ip.object_inspect', cursor_pos=10, detail_level=1)
     reply = KC.get_shell_msg(timeout=TIMEOUT)
     validate_message(reply, 'object_info_reply', msg_id)
     content = reply['content']
     assert content['found']
-    argspec = content['argspec']
-    nt.assert_is_instance(argspec, dict, "expected non-empty argspec dict, got %r" % argspec)
-    nt.assert_equal(argspec['defaults'], [0])
+    nt.assert_equal(content['name'], 'ip.object_inspect')
+    text = content['data']['text/plain']
+    nt.assert_in('Definition:', text)
+    nt.assert_in('Source:', text)
 
 
 def test_oinfo_not_found():
@@ -368,7 +347,7 @@ def test_complete():
 
     msg_id, reply = execute(code="alpha = albert = 5")
     
-    msg_id = KC.complete('al', 'al', 2)
+    msg_id = KC.complete('al', 2)
     reply = KC.get_shell_msg(timeout=TIMEOUT)
     validate_message(reply, 'complete_reply', msg_id)
     matches = reply['content']['matches']
diff --git a/IPython/kernel/zmq/ipkernel.py b/IPython/kernel/zmq/ipkernel.py
index 4c507f9..9c83213 100755
--- a/IPython/kernel/zmq/ipkernel.py
+++ b/IPython/kernel/zmq/ipkernel.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 """An interactive kernel that talks to frontends over 0MQ."""
 
 # Copyright (c) IPython Development Team.
@@ -28,6 +27,7 @@ from IPython.core import release
 from IPython.utils import py3compat
 from IPython.utils.py3compat import builtin_mod, unicode_type, string_types
 from IPython.utils.jsonutil import json_clean
+from IPython.utils.tokenutil import token_at_cursor
 from IPython.utils.traitlets import (
     Any, Instance, Float, Dict, List, Set, Integer, Unicode,
     Type, Bool,
@@ -243,6 +243,7 @@ class Kernel(Configurable):
         else:
             # ensure default_int_handler during handler call
             sig = signal(SIGINT, default_int_handler)
+            self.log.debug("%s: %s", msg_type, msg)
             try:
                 handler(stream, idents, msg)
             except Exception:
@@ -489,7 +490,11 @@ class Kernel(Configurable):
         self._publish_status(u'idle', parent)
 
     def complete_request(self, stream, ident, parent):
-        txt, matches = self._complete(parent)
+        content = parent['content']
+        code = content['code']
+        cursor_pos = content['cursor_pos']
+        
+        txt, matches = self.shell.complete('', code, cursor_pos)
         matches = {'matches' : matches,
                    'matched_text' : txt,
                    'status' : 'ok'}
@@ -500,13 +505,25 @@ class Kernel(Configurable):
 
     def object_info_request(self, stream, ident, parent):
         content = parent['content']
-        object_info = self.shell.object_inspect(content['oname'],
-                        detail_level = content.get('detail_level', 0)
-        )
+        
+        name = token_at_cursor(content['code'], content['cursor_pos'])
+        info = self.shell.object_inspect(name)
+        
+        reply_content = {'status' : 'ok'}
+        reply_content['data'] = data = {}
+        reply_content['metadata'] = {}
+        reply_content['name'] = name
+        reply_content['found'] = info['found']
+        if info['found']:
+            info_text = self.shell.object_inspect_text(
+                name,
+                detail_level=content.get('detail_level', 0),
+            )
+            reply_content['data']['text/plain'] = info_text
         # Before we send this object over, we scrub it for JSON usage
-        oinfo = json_clean(object_info)
+        reply_content = json_clean(reply_content)
         msg = self.session.send(stream, 'object_info_reply',
-                                oinfo, parent, ident)
+                                reply_content, parent, ident)
         self.log.debug("%s", msg)
 
     def history_request(self, stream, ident, parent):
@@ -822,19 +839,6 @@ class Kernel(Configurable):
             raise EOFError
         return value
 
-    def _complete(self, msg):
-        c = msg['content']
-        try:
-            cpos = int(c['cursor_pos'])
-        except:
-            # If we don't get something that we can convert to an integer, at
-            # least attempt the completion guessing the cursor is at the end of
-            # the text, if there's any, and otherwise of the line
-            cpos = len(c['text'])
-            if cpos==0:
-                cpos = len(c['line'])
-        return self.shell.complete(c['text'], c['line'], cpos)
-
     def _at_shutdown(self):
         """Actions taken at shutdown by the kernel, called by python's atexit.
         """
diff --git a/IPython/kernel/zmq/tests/test_embed_kernel.py b/IPython/kernel/zmq/tests/test_embed_kernel.py
index 4449431..fbdb02d 100644
--- a/IPython/kernel/zmq/tests/test_embed_kernel.py
+++ b/IPython/kernel/zmq/tests/test_embed_kernel.py
@@ -128,7 +128,8 @@ def test_embed_kernel_basic():
         msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
         content = msg['content']
         nt.assert_true(content['found'])
-        nt.assert_equal(content['string_form'], u'10')
+        text = content['data']['text/plain']
+        nt.assert_in('10', text)
 
 def test_embed_kernel_namespace():
     """IPython.embed_kernel() inherits calling namespace"""
@@ -148,14 +149,16 @@ def test_embed_kernel_namespace():
         msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
         content = msg['content']
         nt.assert_true(content['found'])
-        nt.assert_equal(content['string_form'], u'5')
+        text = content['data']['text/plain']
+        nt.assert_in(u'5', text)
 
         # oinfo b (str)
         msg_id = client.object_info('b')
         msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
         content = msg['content']
         nt.assert_true(content['found'])
-        nt.assert_equal(content['string_form'], u'hi there')
+        text = content['data']['text/plain']
+        nt.assert_in(u'hi there', text)
 
         # oinfo c (undefined)
         msg_id = client.object_info('c')
@@ -184,7 +187,8 @@ def test_embed_kernel_reentrant():
             msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
             content = msg['content']
             nt.assert_true(content['found'])
-            nt.assert_equal(content['string_form'], unicode_type(i))
+            text = content['data']['text/plain']
+            nt.assert_in(unicode_type(i), text)
             
             # exit from embed_kernel
             client.execute("get_ipython().exit_now = True")
diff --git a/IPython/kernel/zmq/tests/test_start_kernel.py b/IPython/kernel/zmq/tests/test_start_kernel.py
index a31c1f3..0113255 100644
--- a/IPython/kernel/zmq/tests/test_start_kernel.py
+++ b/IPython/kernel/zmq/tests/test_start_kernel.py
@@ -14,7 +14,8 @@ def test_ipython_start_kernel_userns():
         msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
-        nt.assert_equal(content['string_form'], u'123')
+        text = content['data']['text/plain']
+        nt.assert_in(u'123', text)
 
         # user_module should be an instance of DummyMod
         msg_id = client.execute("usermod = get_ipython().user_module")
@@ -25,7 +26,8 @@ def test_ipython_start_kernel_userns():
         msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
-        nt.assert_in('DummyMod', content['string_form'])
+        text = content['data']['text/plain']
+        nt.assert_in(u'DummyMod', text)
 
 def test_ipython_start_kernel_no_userns():
     # Issue #4188 - user_ns should be passed to shell as None, not {}
@@ -42,4 +44,5 @@ def test_ipython_start_kernel_no_userns():
         msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
-        nt.assert_not_in('DummyMod', content['string_form'])
+        text = content['data']['text/plain']
+        nt.assert_not_in(u'DummyMod', text)
diff --git a/IPython/qt/console/console_widget.py b/IPython/qt/console/console_widget.py
index 38078c3..bf85310 100644
--- a/IPython/qt/console/console_widget.py
+++ b/IPython/qt/console/console_widget.py
@@ -1584,7 +1584,16 @@ class ConsoleWidget(MetaQObjectHasTraits('NewBase', (LoggingConfigurable, QtGui.
             cursor = self._control.textCursor()
             text = self._get_block_plain_text(cursor.block())
             return text[len(prompt):]
-
+    
+    def _get_input_buffer_cursor_pos(self):
+        """Return the cursor position within the input buffer."""
+        cursor = self._control.textCursor()
+        cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
+        input_buffer = cursor.selection().toPlainText()
+        
+        # Don't count continuation prompts
+        return len(input_buffer.replace('\n' + self._continuation_prompt, '\n'))
+    
     def _get_input_buffer_cursor_prompt(self):
         """ Returns the (plain text) prompt for line of the input buffer that
             contains the cursor, or None if there is no such line.
diff --git a/IPython/qt/console/frontend_widget.py b/IPython/qt/console/frontend_widget.py
index 412f58f..651bdb4 100644
--- a/IPython/qt/console/frontend_widget.py
+++ b/IPython/qt/console/frontend_widget.py
@@ -727,17 +727,10 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
         # Decide if it makes sense to show a call tip
         if not self.enable_calltips:
             return False
-        cursor = self._get_cursor()
-        cursor.movePosition(QtGui.QTextCursor.Left)
-        if cursor.document().characterAt(cursor.position()) != '(':
-            return False
-        context = self._get_context(cursor)
-        if not context:
-            return False
-
+        cursor_pos = self._get_input_buffer_cursor_pos()
+        code = self.input_buffer
         # Send the metadata request to the kernel
-        name = '.'.join(context)
-        msg_id = self.kernel_client.object_info(name)
+        msg_id = self.kernel_client.object_info(code, cursor_pos)
         pos = self._get_cursor().position()
         self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
         return True
@@ -749,10 +742,9 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
         if context:
             # Send the completion request to the kernel
             msg_id = self.kernel_client.complete(
-                '.'.join(context),                       # text
-                self._get_input_buffer_cursor_line(),    # line
-                self._get_input_buffer_cursor_column(),  # cursor_pos
-                self.input_buffer)                       # block
+                code=self.input_buffer,
+                cursor_pos=self._get_input_buffer_cursor_pos(),
+            )
             pos = self._get_cursor().position()
             info = self._CompletionRequest(msg_id, pos)
             self._request_info['complete'] = info
diff --git a/IPython/qt/console/ipython_widget.py b/IPython/qt/console/ipython_widget.py
index 59ab9c7..d5eba3f 100644
--- a/IPython/qt/console/ipython_widget.py
+++ b/IPython/qt/console/ipython_widget.py
@@ -327,24 +327,6 @@ class IPythonWidget(FrontendWidget):
     # 'FrontendWidget' protected interface
     #---------------------------------------------------------------------------
 
-    def _complete(self):
-        """ Reimplemented to support IPython's improved completion machinery.
-        """
-        # We let the kernel split the input line, so we *always* send an empty
-        # text field. Readline-based frontends do get a real text field which
-        # they can use.
-        text = ''
-
-        # Send the completion request to the kernel
-        msg_id = self.kernel_client.shell_channel.complete(
-            text,                                    # text
-            self._get_input_buffer_cursor_line(),    # line
-            self._get_input_buffer_cursor_column(),  # cursor_pos
-            self.input_buffer)                       # block
-        pos = self._get_cursor().position()
-        info = self._CompletionRequest(msg_id, pos)
-        self._request_info['complete'] = info
-
     def _process_execute_error(self, msg):
         """ Reimplemented for IPython-style traceback formatting.
         """
diff --git a/IPython/terminal/console/completer.py b/IPython/terminal/console/completer.py
index 294d347..a85b6e5 100644
--- a/IPython/terminal/console/completer.py
+++ b/IPython/terminal/console/completer.py
@@ -1,6 +1,9 @@
-"""Adapt readline completer interface to make ZMQ request.
-"""
 # -*- coding: utf-8 -*-
+"""Adapt readline completer interface to make ZMQ request."""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
 try:
     from queue import Empty  # Py 3
 except ImportError:
@@ -27,14 +30,16 @@ class ZMQCompleter(IPCompleter):
         self.client =  client
         self.matches = []
         
-    def complete_request(self,text):
+    def complete_request(self, text):
         line = readline.get_line_buffer()
         cursor_pos = readline.get_endidx()
         
         # send completion request to kernel
         # Give the kernel up to 0.5s to respond
-        msg_id = self.client.shell_channel.complete(text=text, line=line,
-                                                        cursor_pos=cursor_pos)
+        msg_id = self.client.shell_channel.complete(
+            code=line,
+            cursor_pos=cursor_pos,
+        )
         
         msg = self.client.shell_channel.get_msg(timeout=self.timeout)
         if msg['parent_header']['msg_id'] == msg_id:
diff --git a/IPython/utils/tests/test_tokenutil.py b/IPython/utils/tests/test_tokenutil.py
index 69e3362..4d7084a 100644
--- a/IPython/utils/tests/test_tokenutil.py
+++ b/IPython/utils/tests/test_tokenutil.py
@@ -6,11 +6,16 @@ import nose.tools as nt
 
 from IPython.utils.tokenutil import token_at_cursor
 
-def expect_token(expected, cell, column, line=0):
-    token = token_at_cursor(cell, column, line)
-    
-    lines = cell.splitlines()
-    line_with_cursor = '%s|%s' % (lines[line][:column], lines[line][column:])
+def expect_token(expected, cell, cursor_pos):
+    token = token_at_cursor(cell, cursor_pos)
+    offset = 0
+    for line in cell.splitlines():
+        if offset + len(line) >= cursor_pos:
+            break
+        else:
+            offset += len(line)
+    column = cursor_pos - offset
+    line_with_cursor = '%s|%s' % (line[:column], line[column:])
     line
     nt.assert_equal(token, expected,
         "Excpected %r, got %r in: %s" % (
@@ -34,11 +39,13 @@ def test_multiline():
         'b = hello("string", there)'
     ])
     expected = 'hello'
-    for i in range(4,9):
-        expect_token(expected, cell, i, 1)
+    start = cell.index(expected)
+    for i in range(start, start + len(expected)):
+        expect_token(expected, cell, i)
     expected = 'there'
-    for i in range(21,27):
-        expect_token(expected, cell, i, 1)
+    start = cell.index(expected)
+    for i in range(start, start + len(expected)):
+        expect_token(expected, cell, i)
 
 def test_attrs():
     cell = "foo(a=obj.attr.subattr)"
diff --git a/IPython/utils/tokenutil.py b/IPython/utils/tokenutil.py
index 6c1fbac..9a14e04 100644
--- a/IPython/utils/tokenutil.py
+++ b/IPython/utils/tokenutil.py
@@ -23,7 +23,7 @@ def generate_tokens(readline):
         # catch EOF error
         return
 
-def token_at_cursor(cell, column, line=0):
+def token_at_cursor(cell, cursor_pos=0):
     """Get the token at a given cursor
     
     Used for introspection.
@@ -33,15 +33,13 @@ def token_at_cursor(cell, column, line=0):
     
     cell : unicode
         A block of Python code
-    column : int
-        The column of the cursor offset, where the token should be found
-    line : int, optional
-        The line where the token should be found (optional if cell is a single line)
+    cursor_pos : int
+        The location of the cursor in the block where the token should be found
     """
     cell = cast_unicode_py2(cell)
     names = []
     tokens = []
-    current_line = 0
+    offset = 0
     for tup in generate_tokens(StringIO(cell).readline):
         
         tok = Token(*tup)
@@ -49,7 +47,7 @@ def token_at_cursor(cell, column, line=0):
         # token, text, start, end, line = tup
         start_col = tok.start[1]
         end_col = tok.end[1]
-        if line == current_line and start_col > column:
+        if offset + start_col > cursor_pos:
             # current token starts after the cursor,
             # don't consume it
             break
@@ -64,13 +62,13 @@ def token_at_cursor(cell, column, line=0):
                 # don't inspect the lhs of an assignment
                 names.pop(-1)
         
-        if line == current_line and end_col > column:
+        if offset + end_col > cursor_pos:
             # we found the cursor, stop reading
             break
         
         tokens.append(tok)
         if tok.token == tokenize2.NEWLINE:
-            current_line += 1
+            offset += len(tok.line)
     
     if names:
         return names[-1]