import unicodedata
from wcwidth import wcwidth

from IPython.utils.py3compat import PY3

from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.layout.lexers import Lexer
from prompt_toolkit.layout.lexers import PygmentsLexer

from pygments.lexers import Python3Lexer, BashLexer, PythonLexer

class IPythonPTCompleter(Completer):
    """Adaptor to provide IPython completions to prompt_toolkit"""
    def __init__(self, ipy_completer):
        self.ipy_completer = ipy_completer

    def get_completions(self, document, complete_event):
        if not document.current_line.strip():
            return

        used, matches = self.ipy_completer.complete(
                            line_buffer=document.current_line,
                            cursor_pos=document.cursor_position_col
        )
        start_pos = -len(used)
        for m in matches:
            if not m:
                # Guard against completion machinery giving us an empty string.
                continue

            m = unicodedata.normalize('NFC', m)

            # When the first character of the completion has a zero length,
            # then it's probably a decomposed unicode character. E.g. caused by
            # the "\dot" completion. Try to compose again with the previous
            # character.
            if wcwidth(m[0]) == 0:
                if document.cursor_position + start_pos > 0:
                    char_before = document.text[document.cursor_position + start_pos - 1]
                    m = unicodedata.normalize('NFC', char_before + m)

                    # Yield the modified completion instead, if this worked.
                    if wcwidth(m[0:1]) == 1:
                        yield Completion(m, start_position=start_pos - 1)
                        continue

            # TODO: Use Jedi to determine meta_text
            # (Jedi currently has a bug that results in incorrect information.)
            # meta_text = ''
            # yield Completion(m, start_position=start_pos,
            #                  display_meta=meta_text)
            yield Completion(m, start_position=start_pos)

class IPythonPTLexer(Lexer):
    """
    Wrapper around PythonLexer and BashLexer.
    """
    def __init__(self):
        self.python_lexer = PygmentsLexer(Python3Lexer if PY3 else PythonLexer)
        self.shell_lexer = PygmentsLexer(BashLexer)

    def lex_document(self, cli, document):
        if document.text.startswith('!'):
            return self.shell_lexer.lex_document(cli, document)
        else:
            return self.python_lexer.lex_document(cli, document)

@@ -245,20 +245,22 @@
 sLexer%0A%0A
-from
+import
  pygment
@@ -272,51 +272,27 @@
 ers 
-import Python3Lexer, BashLexer, PythonL
+as pygments_l
 exer
+s%0A
 %0A%0Acl
@@ -2221,16 +2221,44 @@
 (self):%0A
+        l = pygments_lexers%0A
         
@@ -2291,16 +2291,18 @@
 tsLexer(
+l.
 Python3L
@@ -2318,16 +2318,18 @@
 Y3 else 
+l.
 PythonLe
@@ -2374,16 +2374,18 @@
 tsLexer(
+l.
 BashLexe
@@ -2388,16 +2388,409 @@
 Lexer)%0A%0A
+        self.magic_lexers = %7B%0A            'HTML': PygmentsLexer(l.HtmlLexer),%0A            'html': PygmentsLexer(l.HtmlLexer),%0A            'javascript': PygmentsLexer(l.JavascriptLexer),%0A            'js': PygmentsLexer(l.JavascriptLexer),%0A            'perl': PygmentsLexer(l.PerlLexer),%0A            'ruby': PygmentsLexer(l.RubyLexer),%0A            'latex': PygmentsLexer(l.TexLexer),%0A        %7D%0A%0A
     def 
@@ -2824,34 +2824,38 @@
 ument):%0A        
-if
+text =
  document.text.s
@@ -2857,21 +2857,111 @@
 ext.
-startswith('!
+lstrip()%0A%0A        lexer = self.python_lexer%0A%0A        if text.startswith('!') or text.startswith('%25%25bash
 '):%0A
@@ -2968,30 +2968,31 @@
             
-return
+lexer =
  self.shell_
@@ -3000,82 +3000,223 @@
 exer
-.lex_document(cli, document)%0A        else:%0A            return self.python_
+%0A%0A        elif text.startswith('%25%25'):%0A            for magic, l in self.magic_lexers.items():%0A                if text.startswith('%25%25' + magic):%0A                    lexer = l%0A                    break%0A%0A        return 
 lexe

Use correct Pygments lexers for different %%magics.