diff --git a/IPython/html/services/kernels/handlers.py b/IPython/html/services/kernels/handlers.py index b68fc52..a4361c9 100644 --- a/IPython/html/services/kernels/handlers.py +++ b/IPython/html/services/kernels/handlers.py @@ -81,16 +81,23 @@ class ZMQChannelHandler(AuthenticatedZMQStreamHandler): km = self.kernel_manager meth = getattr(km, 'connect_%s' % self.channel) self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession) + # Create a kernel_info channel to query the kernel protocol version. + # This channel will be closed after the kernel_info reply is received. self.kernel_info_channel = None self.kernel_info_channel = km.connect_shell(self.kernel_id) - self.kernel_info_channel.on_recv(self._handle_kernel_info) + self.kernel_info_channel.on_recv(self._handle_kernel_info_reply) self._request_kernel_info() def _request_kernel_info(self): + """send a request for kernel_info""" self.log.debug("requesting kernel info") self.session.send(self.kernel_info_channel, "kernel_info_request") - def _handle_kernel_info(self, msg): + def _handle_kernel_info_reply(self, msg): + """process the kernel_info_reply + + enabling msg spec adaptation, if necessary + """ idents,msg = self.session.feed_identities(msg) try: msg = self.session.unserialize(msg) diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index bddf732..ecac3bd 100644 --- a/IPython/html/static/notebook/js/completer.js +++ b/IPython/html/static/notebook/js/completer.js @@ -171,6 +171,15 @@ var IPython = (function (IPython) { var matches = content.matches; var cur = this.editor.getCursor(); + if (end === null) { + // adapted message spec replies don't have cursor position info, + // interpret end=null as current position, + // and negative start relative to that + end = utils.to_absolute_cursor_pos(this.editor, cur); + if (start < 0) { + start = end + start; + } + } var results = CodeMirror.contextHint(this.editor); var filtered_results = []; //remove results from context completion diff --git a/IPython/kernel/adapter.py b/IPython/kernel/adapter.py index 5d33e97..f15c75f 100644 --- a/IPython/kernel/adapter.py +++ b/IPython/kernel/adapter.py @@ -5,7 +5,7 @@ import json -from IPython.core.release import kernel_protocol_version, kernel_protocol_version_info +from IPython.core.release import kernel_protocol_version_info from IPython.utils.tokenutil import token_at_cursor @@ -45,6 +45,13 @@ class Adapter(object): msg['msg_type'] = header['msg_type'] = self.msg_type_map[msg_type] return msg + def handle_reply_status_error(msg): + """This will be called *instead of* the regular handler + + on any reply with status != ok + """ + return msg + def __call__(self, msg): msg = self.update_header(msg) msg = self.update_metadata(msg) @@ -54,9 +61,10 @@ class Adapter(object): handler = getattr(self, header['msg_type'], None) if handler is None: return msg - # no transform for status=error + + # handle status=error replies separately (no change, at present) if msg['content'].get('status', None) in {'error', 'aborted'}: - return msg + return self.handle_reply_status_error(msg) return handler(msg) def _version_str_to_list(version): @@ -143,7 +151,7 @@ class V5toV4(Adapter): cursor_pos = content['cursor_pos'] line, _ = code_to_line(code, cursor_pos) - new_content = msg['content'] = {'status' : 'ok'} + new_content = msg['content'] = {} new_content['oname'] = token_at_cursor(code, cursor_pos) new_content['detail_level'] = content['detail_level'] return msg @@ -173,12 +181,10 @@ class V5toV4(Adapter): msg['content'].pop('password', None) return msg -def _tuple_to_str(version): - return ".".join(map(str, version)) class V4toV5(Adapter): """Convert msg spec V4 to V5""" - version = kernel_protocol_version + version = '5.0' # invert message renames above msg_type_map = {v:k for k,v in V5toV4.msg_type_map.items()} @@ -227,13 +233,14 @@ class V4toV5(Adapter): return msg def complete_reply(self, msg): - # TODO: complete_reply needs more context than we have - # Maybe strip common prefix and give magic cursor_start = cursor_end = 0? + # complete_reply needs more context than we have to get cursor_start and end. + # use special value of `-1` to indicate to frontend that it should be at + # the current cursor position. content = msg['content'] new_content = msg['content'] = {'status' : 'ok'} - n = len(content['matched_text']) - new_content['matches'] = [ m[n:] for m in content['matches'] ] - new_content['cursor_start'] = new_content['cursor_end'] = 0 + new_content['matches'] = content['matches'] + new_content['cursor_start'] = -len(content['matched_text']) + new_content['cursor_end'] = None new_content['metadata'] = {} return msg diff --git a/IPython/kernel/tests/test_adapter.py b/IPython/kernel/tests/test_adapter.py index 46873c7..8590fd2 100644 --- a/IPython/kernel/tests/test_adapter.py +++ b/IPython/kernel/tests/test_adapter.py @@ -114,13 +114,11 @@ class V4toV5TestCase(AdapterTest): v4, v5 = self.adapt(msg) v4c = v4['content'] v5c = v5['content'] - n = len(v4c['matched_text']) - expected_matches = [ m[n:] for m in v4c['matches'] ] - self.assertEqual(v5c['matches'], expected_matches) + self.assertEqual(v5c['matches'], v4c['matches']) self.assertEqual(v5c['metadata'], {}) - self.assertEqual(v5c['cursor_start'], 0) - self.assertEqual(v5c['cursor_end'], 0) + self.assertEqual(v5c['cursor_start'], -4) + self.assertEqual(v5c['cursor_end'], None) def test_object_info_request(self): msg = self.msg("object_info_request", {