From bc33ecb8b90e56e0f9d219a7ba5f89ef262cab2f 2014-10-25 19:08:22 From: MinRK Date: 2014-10-25 19:08:22 Subject: [PATCH] only complete on current line completion messages are multi-line, but IPython completer code assumes single-line. For now, just pass the current line to the underlying completer. --- diff --git a/IPython/kernel/tests/test_kernel.py b/IPython/kernel/tests/test_kernel.py index 1f9855f..2849da5 100644 --- a/IPython/kernel/tests/test_kernel.py +++ b/IPython/kernel/tests/test_kernel.py @@ -1,16 +1,8 @@ # coding: utf-8 """test the IPython Kernel""" -#------------------------------------------------------------------------------- -# Copyright (C) 2013 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. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import io import os.path @@ -26,10 +18,6 @@ from IPython.utils.tempdir import TemporaryDirectory from .utils import (new_kernel, kernel, TIMEOUT, assemble_output, execute, flush_channels, wait_for_idle) -#------------------------------------------------------------------------------- -# Tests -#------------------------------------------------------------------------------- - def _check_mp_mode(kc, expected=False, stream="stdout"): execute(kc=kc, code="import sys") @@ -213,7 +201,7 @@ def test_is_complete(): reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) assert reply['content']['status'] == 'complete' - # SyntaxError should mean it's complete + # SyntaxError should mean it's complete kc.is_complete('raise = 2') reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) assert reply['content']['status'] == 'invalid' @@ -221,4 +209,20 @@ def test_is_complete(): kc.is_complete('a = [1,\n2,') reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) assert reply['content']['status'] == 'incomplete' - assert reply['content']['indent'] == '' \ No newline at end of file + assert reply['content']['indent'] == '' + +def test_complete(): + with kernel() as kc: + execute(u'a = 1', kc=kc) + wait_for_idle(kc) + cell = 'import IPython\nb = a.' + kc.complete(cell) + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + c = reply['content'] + nt.assert_equal(c['status'], 'ok') + nt.assert_equal(c['cursor_start'], cell.find('a.')) + nt.assert_equal(c['cursor_end'], cell.find('a.') + 2) + matches = c['matches'] + nt.assert_greater(len(matches), 0) + for match in matches: + nt.assert_equal(match[:2], 'a.') diff --git a/IPython/kernel/tests/utils.py b/IPython/kernel/tests/utils.py index b2740b5..399f13e 100644 --- a/IPython/kernel/tests/utils.py +++ b/IPython/kernel/tests/utils.py @@ -79,6 +79,8 @@ def start_global_kernel(): if KM is None: KM, KC = start_new_kernel() atexit.register(stop_global_kernel) + else: + flush_channels(KC) return KC @contextmanager diff --git a/IPython/kernel/zmq/ipkernel.py b/IPython/kernel/zmq/ipkernel.py index 793cd48..becb9f0 100644 --- a/IPython/kernel/zmq/ipkernel.py +++ b/IPython/kernel/zmq/ipkernel.py @@ -6,7 +6,7 @@ import traceback from IPython.core import release from IPython.utils.py3compat import builtin_mod, PY3 -from IPython.utils.tokenutil import token_at_cursor +from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from IPython.utils.traitlets import Instance, Type, Any from IPython.utils.decorators import undoc @@ -186,7 +186,15 @@ class IPythonKernel(KernelBase): return reply_content def do_complete(self, code, cursor_pos): - txt, matches = self.shell.complete('', code, cursor_pos) + # FIXME: IPython completers currently assume single line, + # but completion messages give multi-line context + # For now, extract line from cell, based on cursor_pos: + if cursor_pos is None: + cursor_pos = len(code) + line, offset = line_at_cursor(code, cursor_pos) + line_cursor = cursor_pos - offset + + txt, matches = self.shell.complete('', line, line_cursor) return {'matches' : matches, 'cursor_end' : cursor_pos, 'cursor_start' : cursor_pos - len(txt), diff --git a/IPython/utils/tokenutil.py b/IPython/utils/tokenutil.py index 8cd24bf..d9d333f 100644 --- a/IPython/utils/tokenutil.py +++ b/IPython/utils/tokenutil.py @@ -23,6 +23,34 @@ def generate_tokens(readline): # catch EOF error return +def line_at_cursor(cell, cursor_pos=0): + """Return the line in a cell at a given cursor position + + Used for calling line-based APIs that don't support multi-line input, yet. + + Parameters + ---------- + + cell: text + multiline block of text + cursor_pos: integer + the cursor position + + Returns + ------- + + (line, offset): (text, integer) + The line with the current cursor, and the character offset of the start of the line. + """ + offset = 0 + lines = cell.splitlines(True) + for line in lines: + next_offset = offset + len(line) + if next_offset >= cursor_pos: + break + offset = next_offset + return (line, offset) + def token_at_cursor(cell, cursor_pos=0): """Get the token at a given cursor