Repository: spyder-ide/spyder

Issue:
Editor: Popup showing function arguments only displayed for 3 seconds
## Description

In the editor, when I type the name of a function and a left parenthesis, Spyder displays a helpful popup showing the function's arguments.  Unfortunately, this is only displayed for 3 seconds, and then disappears.  It would be helpful if this popup stayed visible for longer, possibly until I type the closing parenthesis or move the text cursor off of the line.

**What steps will reproduce the problem?**
1. Type the name of a function, followed by the left parenthesis
2. The helpful popup appears
3. Wait 3 seconds
4. The helpful popup disappears, and I am sad
## Version and main components
- Spyder Version: 3.0.0b1 (happens in 2.3.x as well)
- Python Version: 2.7.10
- Qt Versions: 4.8.6, PyQt4 (API v2) 4.10.4 on Windows
## Optional dependencies

```
pyflakes >=0.5.0:  0.9.2 (OK)
pep8 >=0.6      :  1.6.2 (OK)
pygments >=1.6  :  2.0.2 (OK)
IPython >=3.0   :  3.2.0 (OK)
zmq >=13.0.0    :  14.7.0 (OK)
pandas >=0.13.1 :  0.16.2 (OK)
sphinx >=0.6.6  :  1.3.1 (OK)
rope >=0.9.4    :  0.9.4 (OK)
jedi >=0.8.1    :  0.8.1 (OK)
matplotlib >=1.0:  1.4.3 (OK)
sympy >=0.7.3   :  0.7.6 (OK)
pylint >=0.25   :  None (NOK)

```



Discussion:
If you put the mouse over the popup before it disappears, it will remain visible until you do a right-click over the popup or move the mouse somewhere else.

So the user is happily typing away, and has to move their hands to the mouse and put it over the popup in the next three seconds, or the helpful info goes away?  That seems less than ideal.

I would argue that it should be treated the same as the code completion popup, which gets left in place indefinitely, unless the user dismisses it by hitting ESC.

Finally, if the info is not going to be left there indefinitely, it should at least be for longer than three seconds.  Three seconds isn't generally enough time for me to even read the function definition, let along process it and make use of the information.  It's hardly even enough time for me to move the mouse over the popup to keep it from disappearing, unless I'm planning ahead that I'm going to do it.

Same problem I am facing..

I don't think this is a problem per se (at least in my mind ;-) I still think we should not maintain the tooltip in view all the time.

I mean, having the tooltip visible seems to me very distracting. But we could add an option for users who prefer that.

@joshburnett, @akhouribhanu, what do you think?

@ccordoba12 : Definitely, there should be an option to customise. As you wrote earlier, as long as you keep your mouse over menu, it will be visible. But, as soon as you start typing code, It will be gone. As you know, there are many parameters/arguments available with a function, and one cannot memorise it all after looking it once. When you are in middle of writing a function, and you are stuck as you forget the parameter/arguments you are suppose to use.

@akhouribhanu, ok, I understand the use case better now. Then perhaps it'd be better to add a close button to the tooltip.

@goanpeca, @jitseniesen, what do you think about this one?

Actually @ccordoba12 I do agree with the user suggestion,  I have find myself deleting the `(` and retyping it to get the info.

We could try to leave it there until the cursor changes to a different line (after going to the end of the line and pressing enter... or moving the cursor with the arrows up or down).

This would leave the tooltip for the needed time :-), while typing in the same line. Maybe is not perfect, but having a close button completely defeats the purpose of a mouse free interaction (which the user and I myself prefer)

**Update**

Or using Esc as the user suggests!

@goanpeca wrote down exactly what I thought: leave the tooltip until the corresponding closing parenthesis is typed (or the user moves the cursor past the automatically inserted closing parenthesis), or the cursor is moved to another line, or the user presses **Esc**, or another tooltip is displayed.

Not sure what to do if the argument list spans more than one line; does the tooltip move down or is it closed?

PS: Visual Studio displays the argument that you are currently at in bold in the tooltip, which I think is pretty neat.

These are all very good suggestions. I'll create a PR to test how this works :-)

@Nodd, I think this would require some changes in your `spyder_vim` plugin because if a tooltip is visible, users will need to press `Esc` twice now: one time to hide the tooltip and another one to enter command mode.

> Visual Studio displays the argument that you are currently at in bold in the tooltip

Interesting idea, but for another PR ;-)


Resolving patch:
diff --git a/spyder/widgets/calltip.py b/spyder/widgets/calltip.py
--- a/spyder/widgets/calltip.py
+++ b/spyder/widgets/calltip.py
@@ -41,6 +41,7 @@ def __init__(self, text_edit, hide_timer_on=False):
         self.app = QCoreApplication.instance()
 
         self.hide_timer_on = hide_timer_on
+        self.tip = None
         self._hide_timer = QBasicTimer()
         self._text_edit = text_edit
 
@@ -65,7 +66,7 @@ def eventFilter(self, obj, event):
             if etype == QEvent.KeyPress:
                 key = event.key()
                 if key in (Qt.Key_Enter, Qt.Key_Return,
-                           Qt.Key_Down):
+                           Qt.Key_Down, Qt.Key_Up):
                     self.hide()
                 elif key == Qt.Key_Escape:
                     self.hide()
@@ -145,6 +146,12 @@ def showEvent(self, event):
             self._cursor_position_changed)
         self._text_edit.installEventFilter(self)
 
+    def focusOutEvent(self, event):
+        """ Reimplemented to hide it when focus goes out of the main
+            window.
+        """
+        self.hide()
+
     #--------------------------------------------------------------------------
     # 'CallTipWidget' interface
     #--------------------------------------------------------------------------
@@ -152,6 +159,14 @@ def showEvent(self, event):
     def show_tip(self, point, tip, wrapped_tiplines):
         """ Attempts to show the specified tip at the current cursor location.
         """
+        # Don't attempt to show it if it's already visible and the text
+        # to be displayed is the same as the one displayed before.
+        if self.isVisible():
+            if self.tip == tip:
+                return True
+            else:
+                self.hide()
+
         # Attempt to find the cursor position at which to show the call tip.
         text_edit = self._text_edit
         cursor = text_edit.textCursor()
@@ -186,6 +201,7 @@ def show_tip(self, point, tip, wrapped_tiplines):
             self._hide_timer.start(hide_time, self)
 
         # Set the text and resize the widget accordingly.
+        self.tip = tip
         self.setText(tip)
         self.resize(self.sizeHint())
 
@@ -273,7 +289,7 @@ def _leave_event_hide(self):
         """ Hides the tooltip after some time has passed (assuming the cursor is
             not over the tooltip).
         """
-        if (not self._hide_timer.isActive() and
+        if (self.hide_timer_on and not self._hide_timer.isActive() and
             # If Enter events always came after Leave events, we wouldn't need
             # this check. But on Mac OS, it sometimes happens the other way
             # around when the tooltip is created.
@@ -286,10 +302,12 @@ def _cursor_position_changed(self):
         """ Updates the tip based on user cursor movement.
         """
         cursor = self._text_edit.textCursor()
-        if cursor.position() <= self._start_position:
+        position = cursor.position()
+        document = self._text_edit.document()
+        char = to_text_string(document.characterAt(position - 1))
+        if position <= self._start_position:
             self.hide()
-        else:
-            if not self.hide_timer_on:
-                position, commas = self._find_parenthesis(self._start_position + 1)
-                if position != -1:
-                    self.hide()
+        elif char == ')':
+            pos, _ = self._find_parenthesis(position - 1, forward=False)
+            if pos == -1:
+                self.hide()
diff --git a/spyder/widgets/ipythonconsole/control.py b/spyder/widgets/ipythonconsole/control.py
--- a/spyder/widgets/ipythonconsole/control.py
+++ b/spyder/widgets/ipythonconsole/control.py
@@ -32,7 +32,7 @@ def __init__(self, parent=None):
         TracebackLinksMixin.__init__(self)
         GetHelpMixin.__init__(self)
 
-        self.calltip_widget = CallTipWidget(self, hide_timer_on=True)
+        self.calltip_widget = CallTipWidget(self, hide_timer_on=False)
         self.found_results = []
 
         # To not use Spyder calltips obtained through the monitor
diff --git a/spyder/widgets/sourcecode/base.py b/spyder/widgets/sourcecode/base.py
--- a/spyder/widgets/sourcecode/base.py
+++ b/spyder/widgets/sourcecode/base.py
@@ -255,7 +255,7 @@ def __init__(self, parent=None):
         self.completion_text = ""
         self.setup_completion()
 
-        self.calltip_widget = CallTipWidget(self, hide_timer_on=True)
+        self.calltip_widget = CallTipWidget(self, hide_timer_on=False)
         self.calltips = True
         self.calltip_position = None
 


Test patch: