Repository: ipython/ipython

Issue:
Inspect requests inside a function call should be smarter about what they inspect.
Previously, `func(a, b, <shift-tab>` would give information on `func`, now it gives information on `b`, which is not especially helpful.

This is because we removed logic from the frontend to make it more language agnostic, and we have not yet reimplemented that on the frontend. For 3.1, we should make it at least as smart as 2.x was. The quicky and dirty approach would be a regex; the proper way is tokenising the code.

Ping @mwaskom who brought this up on the mailing list.



Discussion:
Thanks! I don't actually know how to _use_ any of these packages, so I rely on what IPython tells me they'll do :)

Should note here too that the help also seems to be displaying the `__repr__` for, at least, pandas DataFrames slightly differently in 3.0.rc1, which yields a help popup that is garbled and hides the important bits.

The dataframe reprs sounds like a separate thing - can you file an issue for it? Preferably with screenshots? Thanks.

Done: #7817 

More related to this issue:

While implementing a smarter inspector, it would be _great_ if it would work across line breaks. I'm constantly getting bitten by trying to do

``` python
complex_function(some_arg, another_arg, data_frame.some_transformation(),
                 a_kwarg=a_value, <shift-TAB>
```

And having it not work.

This did not work on the 2.x series either, AFAICT, but if the inspector is going to be reimplemented it would be awesome if it could be added.

If there's smart, tokenising logic to determine what you're inspecting, there's no reason it shouldn't handle multiple lines. Making it smart enough for that might not be a 3.1 thing, though.


Resolving patch:
diff --git a/IPython/utils/tokenutil.py b/IPython/utils/tokenutil.py
--- a/IPython/utils/tokenutil.py
+++ b/IPython/utils/tokenutil.py
@@ -58,6 +58,9 @@ def token_at_cursor(cell, cursor_pos=0):
     
     Used for introspection.
     
+    Function calls are prioritized, so the token for the callable will be returned
+    if the cursor is anywhere inside the call.
+    
     Parameters
     ----------
     
@@ -70,6 +73,7 @@ def token_at_cursor(cell, cursor_pos=0):
     names = []
     tokens = []
     offset = 0
+    call_names = []
     for tup in generate_tokens(StringIO(cell).readline):
         
         tok = Token(*tup)
@@ -93,6 +97,11 @@ def token_at_cursor(cell, cursor_pos=0):
             if tok.text == '=' and names:
                 # don't inspect the lhs of an assignment
                 names.pop(-1)
+            if tok.text == '(' and names:
+                # if we are inside a function call, inspect the function
+                call_names.append(names[-1])
+            elif tok.text == ')' and call_names:
+                call_names.pop(-1)
         
         if offset + end_col > cursor_pos:
             # we found the cursor, stop reading
@@ -102,7 +111,9 @@ def token_at_cursor(cell, cursor_pos=0):
         if tok.token == tokenize2.NEWLINE:
             offset += len(tok.line)
     
-    if names:
+    if call_names:
+        return call_names[-1]
+    elif names:
         return names[-1]
     else:
         return ''


Test patch:
diff --git a/IPython/utils/tests/test_tokenutil.py b/IPython/utils/tests/test_tokenutil.py
--- a/IPython/utils/tests/test_tokenutil.py
+++ b/IPython/utils/tests/test_tokenutil.py
@@ -48,13 +48,28 @@ def test_multiline():
     start = cell.index(expected) + 1
     for i in range(start, start + len(expected)):
         expect_token(expected, cell, i)
-    expected = 'there'
+    expected = 'hello'
     start = cell.index(expected) + 1
     for i in range(start, start + len(expected)):
         expect_token(expected, cell, i)
 
+def test_nested_call():
+    cell = "foo(bar(a=5), b=10)"
+    expected = 'foo'
+    start = cell.index('bar') + 1
+    for i in range(start, start + 3):
+        expect_token(expected, cell, i)
+    expected = 'bar'
+    start = cell.index('a=')
+    for i in range(start, start + 3):
+        expect_token(expected, cell, i)
+    expected = 'foo'
+    start = cell.index(')') + 1
+    for i in range(start, len(cell)-1):
+        expect_token(expected, cell, i)
+
 def test_attrs():
-    cell = "foo(a=obj.attr.subattr)"
+    cell = "a = obj.attr.subattr"
     expected = 'obj'
     idx = cell.find('obj') + 1
     for i in range(idx, idx + 3):