Repository: ipython/ipython

Issue:
Method help popovers have become less useful in IPython 3.0.rc1
Forking from #7816 

I've noticed that the help popover (i.e., what you get when you hit shit-TAB in side the text of a function call) now has a lot of information that is not very useful to the task of finding out the function signature for a method.

I've particularly noticed this for Pandas Dataframes, though I assume the issue is more general than that. It's particularly annoying with DataFrames because the textual `__repr__` looks really bad, especially for large frames. (And also because I can never remember how to use their methods).

Here's an example of what we used to get:

![screenshot 2015-02-19 10 38 29](https://cloud.githubusercontent.com/assets/315810/6273369/dc816412-b823-11e4-8a8b-e0aeea040d93.png)

And here's what we get now:

![screenshot 2015-02-19 10 37 44](https://cloud.githubusercontent.com/assets/315810/6273378/e5146200-b823-11e4-86ad-070859bd5e80.png)

(I should note that putting together this issue caused me to note how much more attractive I find the 3.0/Jupyter styling. Well done!)



Discussion:
Aha, yes, this was another part of the reorganisation to make the frontend more language agnostic. Previously, the tooltip got a bunch of semi-semantic but quite Python specific info, and that was formatted by JS code. Now it gets text back from the kernel and displays it as is, so what you see is the same as when you do `tips.groupby?`.

I've also noticed that this information is not brilliant for tooltip presentation - see issue #7759 for some reorganisation I want to do.

I also have a more ambitious plan with @ccordoba12 to adapt some code from Spyder to render a nicer HTML presentation of docstrings.

@takluyver 

> I also have a more ambitious plan with @ccordoba12 to adapt some code from Spyder to render a nicer HTML presentation of docstrings.

Do you mean to render the markdown as HTML inside the tooltip? If that is correct, it would be great to see LaTeX rendering via MathJax.

I do mean rendering as HTML inside tooltips, but there's not currently any markdown involved. I think that MathJax rendering should _just work_ in that situation.

Oh, right. I think it is reST, not markdown. 

I remember that you have to tell MathJax to target a certain element to dynamically render the math. In any case, that is excellent.

I thought it just looked for text in `$foo$`, but maybe we do that and put it in some element for mathjax to find.

Yes, I'm guessing that's the way you're doing it (it is called [inline configuration](http://docs.mathjax.org/en/latest/dynamic.html))

I second this issue, in my opinion it was nicer when we had function definition and docstring directly on alt+tab. I raised the concern on reddit and apparently other people agree (see [here](http://www.reddit.com/r/Python/comments/2xfw0i/ipython_30_released/cozua95)). Would it be possible to put the "Definition" and "Docstring" fields at the top ?

Yep, that's exactly what I plan to do. I will work on it today, since this seems to be a concern for a lot of people.


Resolving patch:
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -34,7 +34,7 @@
 from IPython.core import prefilter
 from IPython.core import shadowns
 from IPython.core import ultratb
-from IPython.core.alias import AliasManager, AliasError
+from IPython.core.alias import Alias, AliasManager
 from IPython.core.autocall import ExitAutocall
 from IPython.core.builtin_trap import BuiltinTrap
 from IPython.core.events import EventManager, available_events
@@ -1502,6 +1502,7 @@ def _ofind(self, oname, namespaces=None):
                 found = True
                 ospace = 'IPython internal'
                 ismagic = True
+                isalias = isinstance(obj, Alias)
 
         # Last try: special-case some literals like '', [], {}, etc:
         if not found and oname_head in ["''",'""','[]','{}','()']:
diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py
--- a/IPython/core/oinspect.py
+++ b/IPython/core/oinspect.py
@@ -36,6 +36,7 @@
 from IPython.utils import openpy
 from IPython.utils import py3compat
 from IPython.utils.dir2 import safe_hasattr
+from IPython.utils.path import compress_user
 from IPython.utils.text import indent
 from IPython.utils.wildcard import list_namespace
 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
@@ -554,23 +555,6 @@ def _format_fields(self, fields, title_width=0):
                 title = header((title+":").ljust(title_width))
             out.append(cast_unicode(title) + cast_unicode(content))
         return "\n".join(out)
-
-    # The fields to be displayed by pinfo: (fancy_name, key_in_info_dict)
-    pinfo_fields1 = [("Type", "type_name"),
-                    ]
-                    
-    pinfo_fields2 = [("String form", "string_form"),
-                    ]
-
-    pinfo_fields3 = [("Length", "length"),
-                    ("File", "file"),
-                    ("Definition", "definition"),
-                    ]
-
-    pinfo_fields_obj = [("Class docstring", "class_docstring"),
-                        ("Init docstring", "init_docstring"),
-                        ("Call def", "call_def"),
-                        ("Call docstring", "call_docstring")]
     
     def _format_info(self, obj, oname='', formatter=None, info=None, detail_level=0):
         """Format an info dict as text"""
@@ -582,41 +566,62 @@ def add_fields(fields):
                 field = info[key]
                 if field is not None:
                     displayfields.append((title, field.rstrip()))
-        
-        add_fields(self.pinfo_fields1)
-        
-        # Base class for old-style instances
-        if (not py3compat.PY3) and isinstance(obj, types.InstanceType) and info['base_class']:
-            displayfields.append(("Base Class", info['base_class'].rstrip()))
-        
-        add_fields(self.pinfo_fields2)
-        
-        # Namespace
-        if info['namespace'] != 'Interactive':
-            displayfields.append(("Namespace", info['namespace'].rstrip()))
-
-        add_fields(self.pinfo_fields3)
-        if info['isclass'] and info['init_definition']:
-            displayfields.append(("Init definition",
-                            info['init_definition'].rstrip()))
-        
-        # Source or docstring, depending on detail level and whether
-        # source found.
-        if detail_level > 0 and info['source'] is not None:
-            displayfields.append(("Source", 
-                                  self.format(cast_unicode(info['source']))))
-        elif info['docstring'] is not None:
-            displayfields.append(("Docstring", info["docstring"]))
-
-        # Constructor info for classes
-        if info['isclass']:
-            if info['init_docstring'] is not None:
-                displayfields.append(("Init docstring",
-                                    info['init_docstring']))
-
-        # Info for objects:
+
+        if info['isalias']:
+            add_fields([('Repr', "string_form")])
+
+        elif info['ismagic']:
+            add_fields([("Docstring", "docstring"),
+                        ("File", "file")
+                       ])
+
+        elif info['isclass'] or is_simple_callable(obj):
+            # Functions, methods, classes
+            add_fields([("Signature", "definition"),
+                        ("Init signature", "init_definition"),
+                       ])
+            if detail_level > 0 and info['source'] is not None:
+                add_fields([("Source", "source")])
+            else:
+                add_fields([("Docstring", "docstring"),
+                            ("Init docstring", "init_docstring"),
+                           ])
+
+            add_fields([('File', 'file'),
+                        ('Type', 'type_name'),
+                       ])
+
         else:
-            add_fields(self.pinfo_fields_obj)
+            # General Python objects
+            add_fields([("Type", "type_name")])
+
+            # Base class for old-style instances
+            if (not py3compat.PY3) and isinstance(obj, types.InstanceType) and info['base_class']:
+                displayfields.append(("Base Class", info['base_class'].rstrip()))
+
+            add_fields([("String form", "string_form")])
+
+            # Namespace
+            if info['namespace'] != 'Interactive':
+                displayfields.append(("Namespace", info['namespace'].rstrip()))
+
+            add_fields([("Length", "length"),
+                        ("File", "file"),
+                        ("Signature", "definition"),
+                       ])
+
+            # Source or docstring, depending on detail level and whether
+            # source found.
+            if detail_level > 0 and info['source'] is not None:
+                displayfields.append(("Source",
+                                      self.format(cast_unicode(info['source']))))
+            elif info['docstring'] is not None:
+                displayfields.append(("Docstring", info["docstring"]))
+
+            add_fields([("Class docstring", "class_docstring"),
+                        ("Init docstring", "init_docstring"),
+                        ("Call signature", "call_def"),
+                        ("Call docstring", "call_docstring")])
         
         if displayfields:
             return self._format_fields(displayfields)
@@ -737,7 +742,7 @@ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
                 binary_file = True
             elif fname.endswith('<string>'):
                 fname = 'Dynamically generated function. No source code available.'
-            out['file'] = fname
+            out['file'] = compress_user(fname)
 
         # Original source code for a callable, class or property.
         if detail_level:


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
@@ -28,6 +28,7 @@
                                 register_line_cell_magic)
 from IPython.external.decorator import decorator
 from IPython.testing.decorators import skipif
+from IPython.utils.path import compress_user
 from IPython.utils import py3compat
 
 
@@ -46,7 +47,7 @@
 # defined, if any code is inserted above, the following line will need to be
 # updated.  Do NOT insert any whitespace between the next line and the function
 # definition below.
-THIS_LINE_NUMBER = 49  # Put here the actual number of this line
+THIS_LINE_NUMBER = 50  # Put here the actual number of this line
 def test_find_source_lines():
     nt.assert_equal(oinspect.find_source_lines(test_find_source_lines), 
                     THIS_LINE_NUMBER+1)
@@ -241,7 +242,7 @@ def test_info():
         fname = fname[:-1]
     # case-insensitive comparison needed on some filesystems
     # e.g. Windows:
-    nt.assert_equal(i['file'].lower(), fname.lower())
+    nt.assert_equal(i['file'].lower(), compress_user(fname.lower()))
     nt.assert_equal(i['definition'], None)
     nt.assert_equal(i['docstring'], Call.__doc__)
     nt.assert_equal(i['source'], None)
diff --git a/IPython/kernel/tests/test_message_spec.py b/IPython/kernel/tests/test_message_spec.py
--- a/IPython/kernel/tests/test_message_spec.py
+++ b/IPython/kernel/tests/test_message_spec.py
@@ -384,7 +384,7 @@ def test_oinfo_detail():
     content = reply['content']
     assert content['found']
     text = content['data']['text/plain']
-    nt.assert_in('Definition:', text)
+    nt.assert_in('Signature:', text)
     nt.assert_in('Source:', text)