Repository: ipython/ipython

Issue:
mangled ASCII color codes(?) in __init__ signatures
IPython 5.0b3, PTK master, empty `ipython_config.py`.

```
In [6]: class C:
   ...:     def __init__(self): pass
   ...: 
   ...: 

In [7]: C??
Init signature:
[0mC[0m[1;33m([0m[1;33m)[0m[1;33m[0m[0m

*** ERROR: EOF in multi-line statement


Type:
type
```



Discussion:
It is the colorama package which should be interpreting colour codes on Windows. Can you check that you have that and that it's up to date? I'm pretty sure I did test coloured output on Windows too.

This is on Linux.

OK, I see it too, but don't have time to look into it right now.

Introduced by #9551.


Resolving patch:
diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py
--- a/IPython/core/oinspect.py
+++ b/IPython/core/oinspect.py
@@ -593,13 +593,29 @@ def _mime_format(self, text, formatter=None):
             else:
                 return dict(defaults, **formatted)
 
+
+    def format_mime(self, bundle):
+
+        text_plain = bundle['text/plain']
+
+        text = ''
+        heads, bodies = list(zip(*text_plain))
+        _len = max(len(h) for h in heads)
+
+        for head, body in zip(heads, bodies):
+            delim = '\n' if '\n' in body else ' '
+            text += self.__head(head+':') + (_len - len(head))*' ' +delim + body +'\n'
+
+        bundle['text/plain'] = text
+        return bundle
+
     def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0):
         """Retrieve an info dict and format it."""
 
         info = self._info(obj, oname=oname, info=info, detail_level=detail_level)
 
-        mime = {
-            'text/plain': '',
+        _mime = {
+            'text/plain': [],
             'text/html': '',
         }
 
@@ -607,7 +623,7 @@ def append_field(bundle, title, key, formatter=None):
             field = info[key]
             if field is not None:
                 formatted_field = self._mime_format(field, formatter)
-                bundle['text/plain'] += self.__head(title + ':') + '\n' + formatted_field['text/plain'] + '\n'
+                bundle['text/plain'].append((title, formatted_field['text/plain']))
                 bundle['text/html'] += '<h1>' + title + '</h1>\n' + formatted_field['text/html'] + '\n'
 
         def code_formatter(text):
@@ -617,59 +633,61 @@ def code_formatter(text):
             }
 
         if info['isalias']:
-            append_field(mime, 'Repr', 'string_form')
+            append_field(_mime, 'Repr', 'string_form')
 
         elif info['ismagic']:
             if detail_level > 0:
-                append_field(mime, 'Source', 'source', code_formatter)
+                append_field(_mime, 'Source', 'source', code_formatter)
             else:
-                append_field(mime, 'Docstring', 'docstring', formatter)
-            append_field(mime, 'File', 'file')
+                append_field(_mime, 'Docstring', 'docstring', formatter)
+            append_field(_mime, 'File', 'file')
 
         elif info['isclass'] or is_simple_callable(obj):
             # Functions, methods, classes
-            append_field(mime, 'Signature', 'definition', code_formatter)
-            append_field(mime, 'Init signature', 'init_definition', code_formatter)
+            append_field(_mime, 'Signature', 'definition', code_formatter)
+            append_field(_mime, 'Init signature', 'init_definition', code_formatter)
             if detail_level > 0:
-                append_field(mime, 'Source', 'source', code_formatter)
+                append_field(_mime, 'Source', 'source', code_formatter)
             else:
-                append_field(mime, 'Docstring', 'docstring', formatter)
-                append_field(mime, 'Init docstring', 'init_docstring', formatter)
+                append_field(_mime, 'Docstring', 'docstring', formatter)
+                append_field(_mime, 'Init docstring', 'init_docstring', formatter)
 
-            append_field(mime, 'File', 'file')
-            append_field(mime, 'Type', 'type_name')
+            append_field(_mime, 'File', 'file')
+            append_field(_mime, 'Type', 'type_name')
 
         else:
             # General Python objects
-            append_field(mime, 'Type', 'type_name')
+            append_field(_mime, 'Type', 'type_name')
 
             # Base class for old-style instances
             if (not py3compat.PY3) and isinstance(obj, types.InstanceType) and info['base_class']:
-                append_field(mime, 'Base Class', 'base_class')
+                append_field(_mime, 'Base Class', 'base_class')
 
-            append_field(mime, 'String form', 'string_form')
+            append_field(_mime, 'String form', 'string_form')
 
             # Namespace
             if info['namespace'] != 'Interactive':
-                append_field(mime, 'Namespace', 'namespace')
+                append_field(_mime, 'Namespace', 'namespace')
 
-            append_field(mime, 'Length', 'length')
-            append_field(mime, 'File', 'file'),
-            append_field(mime, 'Signature', 'definition', code_formatter)
+            append_field(_mime, 'Length', 'length')
+            append_field(_mime, 'File', 'file'),
+            append_field(_mime, 'Signature', 'definition', code_formatter)
 
             # Source or docstring, depending on detail level and whether
             # source found.
             if detail_level > 0:
-                append_field(mime, 'Source', 'source', code_formatter)
+                append_field(_mime, 'Source', 'source', code_formatter)
             else:
-                append_field(mime, 'Docstring', 'docstring', formatter)
+                append_field(_mime, 'Docstring', 'docstring', formatter)
+
+            append_field(_mime, 'Class docstring', 'class_docstring', formatter)
+            append_field(_mime, 'Init docstring', 'init_docstring', formatter)
+            append_field(_mime, 'Call signature', 'call_def', code_formatter)
+            append_field(_mime, 'Call docstring', 'call_docstring', formatter)
+
 
-            append_field(mime, 'Class docstring', 'class_docstring', formatter)
-            append_field(mime, 'Init docstring', 'init_docstring', formatter)
-            append_field(mime, 'Call signature', 'call_def', code_formatter)
-            append_field(mime, 'Call docstring', 'call_docstring', formatter)
 
-        return mime
+        return self.format_mime(_mime)
 
     def pinfo(self, obj, oname='', formatter=None, info=None, detail_level=0, enable_html_pager=True):
         """Show detailed information about an object.
@@ -831,7 +849,7 @@ def _info(self, obj, oname='', info=None, detail_level=0):
                 init_def = None
 
             if init_def:
-                out['init_definition'] = self.format(init_def)
+                out['init_definition'] = init_def
 
             # get the __init__ docstring
             try:


Test patch:
diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py
--- a/IPython/core/tests/test_oinspect.py
+++ b/IPython/core/tests/test_oinspect.py
@@ -283,7 +283,7 @@ def test_info():
     nt.assert_equal(i['source'], None)
     nt.assert_true(i['isclass'])
     _self_py2 = '' if py3compat.PY3 else 'self, '
-    nt.assert_equal(i['init_definition'], "Call(%sx, y=1)\n" % _self_py2)
+    nt.assert_equal(i['init_definition'], "Call(%sx, y=1)" % _self_py2)
     nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
 
     i = inspector.info(Call, detail_level=1)
@@ -310,12 +310,12 @@ def test_info():
 
 def test_class_signature():
     info = inspector.info(HasSignature, 'HasSignature')
-    nt.assert_equal(info['init_definition'], "HasSignature(test)\n")
+    nt.assert_equal(info['init_definition'], "HasSignature(test)")
     nt.assert_equal(info['init_docstring'], HasSignature.__init__.__doc__)
 
 def test_info_awkward():
     # Just test that this doesn't throw an error.
-    i = inspector.info(Awkward())
+    inspector.info(Awkward())
 
 def test_bool_raise():
     inspector.info(NoBoolCall())