Repository: ipython/ipython

Issue:
QTConsole completion still broken
#6726 made things better, but there is still breakage, tested on git master c829e58f

``` python
np.<tab> # works
x=np.<tab> # works
```

but

``` python
In [3]: from collections import namedtuple
   ...: np.<tab> # nope
   ...: s=np.<tab> # nope
```

doesn't trigger completion.



Discussion:
I confirm the regression, things are working well in `2.3.0`. But @kalibri1798 are you sure you bisected the thing correctly? The commit you referenced has nothing to do with qtconsole.

Thanks for confirming. That was just the git master I tested with. rephrased. 

Another corner case:

``` python
s=''
s.splits # first this
s.split(s # then insert paren 
```

![badtip](https://cloud.githubusercontent.com/assets/7333933/4779373/1656d154-5c2b-11e4-849d-7fee74211bdc.png)

Ok, I'll bisect this issue to see what change introduced the problem. Thanks for the examples.

@ccordoba12 it's the same switch to kernel-side lexing as the related issues.

The `s.split(|s` case is fixed in master by #6774.

The titular case is caused by IPython's custom completer being triggered by the `import` statement on the first line. IPython's completer logic doesn't properly handle the multi-line nature of completion requests at this point.


Resolving patch:
diff --git a/IPython/kernel/zmq/ipkernel.py b/IPython/kernel/zmq/ipkernel.py
--- a/IPython/kernel/zmq/ipkernel.py
+++ b/IPython/kernel/zmq/ipkernel.py
@@ -6,7 +6,7 @@
 
 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 @@ def do_execute(self, code, silent, store_history=True,
         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
--- 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
     


Test patch:
diff --git a/IPython/kernel/tests/test_kernel.py b/IPython/kernel/tests/test_kernel.py
--- 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 .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
--- 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