Repository: ipython/ipython

Issue:
Tab completion problems with cell magics
As mentioned by @takluyver in #1732:

One small flaw I've noticed: if you start the Qt console, and type `%ti<TAB>`, it shows the new `%%timeit` as an option for tab completion, but it reduces the command at the prompt to the longest common prefix, which is now just `%`. It ought to be smarter about that. (This isn't specific to Python 3)

---

While the problem right now appears only in the Qt console, I'm pondering whether we should also change the policy in the terminal, so that the behavior is uniform as it comes from the completer and we don't need custom client-side logic.

I'll work a bit on it and will see if I can come up with something sensible.



Discussion:
I don't think this is just in the Qt console.  I just noticed it in the terminal too.

This was also an issue in notebook, #1829 should fix it.

Fixed in master.

This seem not to be fixed in qtconsole, reopening. 

Yup, confirming the qt bug remains.  I'm less confident now that I can fix it b/c it may entail really qt client-side logic.  I'm removing myself as assignee just to make it clear that I'm not directly working on a fix.

Assigning myself think of doing it for 0.13, but if SO have more bandwidth...


Resolving patch:
diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py
--- a/IPython/core/inputsplitter.py
+++ b/IPython/core/inputsplitter.py
@@ -96,6 +96,10 @@
 ESC_QUOTE2 = ';'     # Quote all args as a single string, call
 ESC_PAREN  = '/'     # Call first argument with rest of line as arguments
 
+ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
+                 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
+                 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
+
 #-----------------------------------------------------------------------------
 # Utilities
 #-----------------------------------------------------------------------------
diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py
--- a/IPython/frontend/qt/console/console_widget.py
+++ b/IPython/frontend/qt/console/console_widget.py
@@ -5,7 +5,7 @@
 #-----------------------------------------------------------------------------
 
 # Standard library imports
-from os.path import commonprefix
+import os.path
 import re
 import sys
 from textwrap import dedent
@@ -16,6 +16,7 @@
 
 # Local imports
 from IPython.config.configurable import LoggingConfigurable
+from IPython.core.inputsplitter import ESC_SEQUENCES
 from IPython.frontend.qt.rich_text import HtmlExporter
 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
 from IPython.utils.text import columnize
@@ -26,10 +27,36 @@
 from completion_plain import CompletionPlain
 from kill_ring import QtKillRing
 
+
 #-----------------------------------------------------------------------------
 # Functions
 #-----------------------------------------------------------------------------
 
+ESCAPE_CHARS = ''.join(ESC_SEQUENCES)
+ESCAPE_RE = re.compile("^["+ESCAPE_CHARS+"]+")
+
+def commonprefix(items):
+    """Get common prefix for completions
+
+    Return the longest common prefix of a list of strings, but with special
+    treatment of escape characters that might precede commands in IPython,
+    such as %magic functions. Used in tab completion.
+
+    For a more general function, see os.path.commonprefix
+    """
+    # the last item will always have the least leading % symbol
+    # min / max are first/last in alphabetical order
+    first_match  = ESCAPE_RE.match(min(items))
+    last_match  = ESCAPE_RE.match(max(items))
+    # common suffix is (common prefix of reversed items) reversed
+    if first_match and last_match:
+        prefix = os.path.commonprefix((first_match.group(0)[::-1], last_match.group(0)[::-1]))[::-1]
+    else:
+        prefix = ''
+
+    items = [s.lstrip(ESCAPE_CHARS) for s in items]
+    return prefix+os.path.commonprefix(items)
+
 def is_letter_or_number(char):
     """ Returns whether the specified unicode character is a letter or a number.
     """


Test patch: