Repository: spyder-ide/spyder

Issue:
Completion hint is somtimes strange when using kite
## Description
![jupyterthemes_hint](https://user-images.githubusercontent.com/22970578/67782425-1bd87400-faac-11e9-9ce5-49196f8b3723.gif)

Completion hint is somtimes strange when using kite.

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

1. install jupyterthemes

2. from jupyterthemes import jtplot

3. jtplot. \<tab\>



## Versions

* Spyder version: 4.0.0.dev0 3b0b10a49
* Python version: 3.7.3
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Linux 5.3.7-2-MANJARO

### Dependencies

```
cloudpickle >=0.5.0          :  1.2.1 (OK)
pygments >=2.0               :  2.4.2 (OK)
qtconsole >=4.5.5            :  4.5.5 (OK)
nbconvert >=4.0              :  5.5.0 (OK)
sphinx >=0.6.6               :  2.1.2 (OK)
pylint >=0.25                :  2.3.1 (OK)
psutil >=0.3                 :  5.6.3 (OK)
qtawesome >=0.5.7            :  0.5.7 (OK)
qtpy >=1.5.0                 :  1.8.0 (OK)
pickleshare >=0.4            :  0.7.5 (OK)
zmq >=17                     :  18.0.0 (OK)
chardet >=2.0.0              :  3.0.4 (OK)
numpydoc >=0.6.0             :  0.9.1 (OK)
spyder_kernels >=1.5.0;<2.0.0:  1.6.0 (OK)
qdarkstyle >=2.7             :  2.7 (OK)
atomicwrites >=1.2.0         :  1.3.0 (OK)
diff_match_patch >=20181111  :  20181111 (OK)
watchdog                     :  None (OK)
keyring                      :  None (OK)
pexpect >=4.4.0              :  4.7.0 (OK)
pympler                      :  None (OK)
sympy >=0.7.3                :  1.4 (OK)
cython >=0.21                :  0.29.12 (OK)
IPython >=4.0                :  7.6.1 (OK)
matplotlib >=2.0.0           :  3.1.0 (OK)
pandas >=0.13.1              :  0.24.2 (OK)
numpy >=1.7                  :  1.16.4 (OK)
scipy >=0.17.0               :  1.3.0 (OK)
pyls >=0.29.2;<0.30.0        :  0.29.3 (OK)
xdg >=0.26                   :  0.26 (OK)
rtree >=0.8.3                :  0.8.3 (OK)
```



Discussion:
@goanpeca, please take a look at this one.
Will do
Thanks a lot!

Resolving patch:
diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py
--- a/spyder/plugins/editor/widgets/codeeditor.py
+++ b/spyder/plugins/editor/widgets/codeeditor.py
@@ -1154,6 +1154,7 @@ def process_signatures(self, params):
         """Handle signature response."""
         try:
             signature_params = params['params']
+
             if (signature_params is not None and
                     'activeParameter' in signature_params):
                 self.sig_signature_invoked.emit(signature_params)
@@ -1440,6 +1441,7 @@ def set_language(self, language, filename=None):
             if self.support_language:
                 self.language = sh_class._lexer.name
         self._set_highlighter(sh_class)
+        self.completion_widget.set_language(self.language)
 
     def _set_highlighter(self, sh_class):
         self.highlighter_class = sh_class
@@ -2177,6 +2179,7 @@ def _set_completions_hint_idle(self):
     def show_hint_for_completion(self, word, documentation, at_point):
         """Show hint for completion element."""
         if self.completions_hint and self._completions_hint_idle:
+            documentation = documentation.replace(u'\xa0', ' ')
             completion_doc = {'name': word,
                               'signature': documentation}
 
diff --git a/spyder/plugins/editor/widgets/completion.py b/spyder/plugins/editor/widgets/completion.py
--- a/spyder/plugins/editor/widgets/completion.py
+++ b/spyder/plugins/editor/widgets/completion.py
@@ -62,7 +62,7 @@ class CompletionWidget(QListWidget):
     def __init__(self, parent, ancestor):
         super(CompletionWidget, self).__init__(ancestor)
         self.textedit = parent
-
+        self._language = None
         self.setWindowFlags(Qt.SubWindow | Qt.FramelessWindowHint)
         self.hide()
         self.itemActivated.connect(self.item_selected)
@@ -95,7 +95,6 @@ def is_empty(self):
 
     def show_list(self, completion_list, position, automatic):
         """Show list corresponding to position."""
-
         if not completion_list:
             self.hide()
             return
@@ -154,6 +153,10 @@ def show_list(self, completion_list, position, automatic):
         # signal used for testing
         self.sig_show_completions.emit(completion_list)
 
+    def set_language(self, language):
+        """Set the completion language."""
+        self._language = language.lower()
+
     def update_list(self, current_word, new=True):
         """
         Update the displayed list by filtering self.completion_list based on
@@ -445,6 +448,14 @@ def trigger_completion_hint(self, row=None):
         else:
             insert_text = item['insertText']
 
+            # Split by starting $ or language specific chars
+            chars = ['$']
+            if self._language == 'python':
+                chars.append('(')
+
+            for ch in chars:
+                insert_text = insert_text.split(ch)[0]
+
         self.sig_completion_hint.emit(
             insert_text,
             item['documentation'],
diff --git a/spyder/widgets/mixins.py b/spyder/widgets/mixins.py
--- a/spyder/widgets/mixins.py
+++ b/spyder/widgets/mixins.py
@@ -439,6 +439,7 @@ def handle_sub(matchobj):
         return '<br>'.join(new_signatures)
 
     def _check_signature_and_format(self, signature_or_text, parameter=None,
+                                    inspect_word=None,
                                     max_width=_DEFAULT_MAX_WIDTH,
                                     language=_DEFAULT_LANGUAGE):
         """
@@ -458,23 +459,41 @@ def _check_signature_and_format(self, signature_or_text, parameter=None,
         signature_or_text = signature_or_text.replace('}', '&#125;')
 
         lines = signature_or_text.split('\n')
-        inspect_word = None
-
         if language == 'python':
             open_func_char = '('
-            end_func_char_line = [')', ',']
-            idx = signature_or_text.find(open_func_char)
-            inspect_word = signature_or_text[:idx]
-            name_plus_char = inspect_word + open_func_char
-
-            # Signature type
-            count = signature_or_text.count(name_plus_char)
-            has_signature = (open_func_char in lines[0]
-                             and lines[0][-1] in end_func_char_line)
-            if len(lines) > 1:
-                has_multisignature = count > 1 and name_plus_char in lines[1]
+            has_multisignature = False
+
+            if inspect_word:
+                has_signature = signature_or_text.startswith(inspect_word)
             else:
-                has_multisignature = False
+                idx = signature_or_text.find(open_func_char)
+                inspect_word = signature_or_text[:idx]
+                has_signature = True
+
+            if has_signature:
+                name_plus_char = inspect_word + open_func_char
+
+                all_lines = []
+                for line in lines:
+                    if (line.startswith(name_plus_char)
+                            and line.count(name_plus_char) > 1):
+                        sublines = line.split(name_plus_char)
+                        sublines = [name_plus_char + l for l in sublines]
+                        sublines = [l.strip() for l in sublines]
+                    else:
+                        sublines = [line]
+
+                    all_lines = all_lines + sublines
+
+                lines = all_lines
+                count = 0
+                for line in lines:
+                    if line.startswith(name_plus_char):
+                        count += 1
+
+                # Signature type
+                has_signature = count == 1
+                has_multisignature = count > 1 and len(lines) > 1
 
         if has_signature and not has_multisignature:
             for i, line in enumerate(lines):
@@ -504,7 +523,6 @@ def _check_signature_and_format(self, signature_or_text, parameter=None,
             signature = signature.replace('<br>', '\n')
             signatures = signature.split('\n')
             signatures = [sig for sig in signatures if sig]  # Remove empty
-
             new_signature = self._format_signature(
                 signatures=signatures,
                 parameter=parameter,
@@ -529,9 +547,11 @@ def show_calltip(self, signature, parameter=None, documentation=None,
         """
         # Find position of calltip
         point = self._calculate_position()
+        signature = signature.strip()
 
         language = getattr(self, 'language', language).lower()
-        if language == 'python' and signature.strip():
+        if language == 'python' and signature:
+            inspect_word = signature.split('(')[0]
             # Check if documentation is better than signature, sometimes
             # signature has \n stripped for functions like print, type etc
             check_doc = ' '
@@ -550,6 +570,7 @@ def show_calltip(self, signature, parameter=None, documentation=None,
 
         # Format
         res = self._check_signature_and_format(signature, parameter,
+                                               inspect_word=inspect_word,
                                                language=language,
                                                max_width=max_width)
         new_signature, text, inspect_word = res
@@ -609,8 +630,8 @@ def show_hint(self, text, inspect_word, at_point,
                   max_width=_DEFAULT_MAX_HINT_WIDTH,
                   text_new_line=True, completion_doc=None):
         """Show code hint and crop text as needed."""
-        # Check if signature and format
-        res = self._check_signature_and_format(text, max_width=max_width)
+        res = self._check_signature_and_format(text, max_width=max_width,
+                                               inspect_word=inspect_word)
         html_signature, extra_text, _ = res
         point = self.get_word_start_pos(at_point)
 


Test patch: