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]