Repository: spyder-ide/spyder

Issue:
Error when triggering completion in the Internal Console
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

1. Type some text in the Internal Console
2. Press tab to trigger completion
3. A wild ERROR appears!

### Traceback
```python-traceback
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\console\widgets\internalshell.py", line 353, in keyPressEvent
    self.postprocess_keyevent(event)
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\console\widgets\shell.py", line 713, in postprocess_keyevent
    ShellBaseWidget.postprocess_keyevent(self, event)
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\console\widgets\shell.py", line 362, in postprocess_keyevent
    self._key_tab()
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\console\widgets\shell.py", line 745, in _key_tab
    self.show_code_completion(automatic=False)
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\console\widgets\shell.py", line 893, in show_code_completion
    automatic=automatic)
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\editor\widgets\base.py", line 1131, in show_completion_list
    self.show_completion_widget(completions, automatic=automatic)
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\editor\widgets\base.py", line 1107, in show_completion_widget
    self.completion_widget.show_list(textlist, automatic=automatic)
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\editor\widgets\base.py", line 112, in show_list
    icon = icons_map.get(completion['kind'], 'no_match')
TypeError: tuple indices must be integers or slices, not str
```

## Versions

* Spyder version: 4.0.0.dev0 ccbea5674
* Python version: 3.6.7
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Windows 8.1

### Dependencies

```
pygments >=2.0    :  2.2.0 (OK)
qdarkstyle >=2.6.4:  2.6.5 (OK)
sphinx >=0.6.6    :  1.8.1 (OK)
pyls >=0.19.0     :  0.19.0 (OK)
nbconvert >=4.0   :  5.3.1 (OK)
pandas >=0.13.1   :  0.23.4 (OK)
numpy >=1.7       :  1.14.3 (OK)
sympy >=0.7.3     :  1.3 (OK)
cython >=0.21     :  0.29 (OK)
qtconsole >=4.2.0 :  4.4.2 (OK)
IPython >=4.0     :  7.0.1 (OK)
matplotlib >=2.0.0:  2.2.2 (OK)
pylint >=0.25     :  2.1.1 (OK)
```



Discussion:
Note that the error printed immediately to the internal console, but when I pressed "up" (on the line right before the prompt) I actually got the completed statement, so completion apparently *did* work.

![image](https://user-images.githubusercontent.com/17051931/51130837-9e30c980-17f3-11e9-8bbf-575e5ba27762.png)

I'll take care of this one.

Resolving patch:
diff --git a/spyder/plugins/console/widgets/shell.py b/spyder/plugins/console/widgets/shell.py
--- a/spyder/plugins/console/widgets/shell.py
+++ b/spyder/plugins/console/widgets/shell.py
@@ -31,7 +31,7 @@
 from spyder.config.gui import config_shortcut, get_shortcut
 from spyder.config.main import CONF
 from spyder.py3compat import (builtins, is_string, is_text_string,
-                              PY3, to_text_string)
+                              PY3, str_lower, to_text_string)
 from spyder.utils import encoding
 from spyder.utils import icon_manager as ima
 from spyder.utils.qthelpers import (add_actions, create_action, keybinding,
@@ -96,7 +96,10 @@ def __init__(self, parent, history_filename, profile=False,
         self.setFocus()
 
         # Cursor width
-        self.setCursorWidth( CONF.get('main', 'cursor/width') )
+        self.setCursorWidth(CONF.get('main', 'cursor/width'))
+
+        # Adjustments to completion_widget to use it here
+        self.completion_widget.currentRowChanged.disconnect()
 
     def toggle_wrap_mode(self, enable):
         """Enable/disable wrap mode"""
@@ -742,12 +745,12 @@ def _key_tab(self):
             if empty_line:
                 self.stdkey_tab()
             else:
-                self.show_code_completion(automatic=False)
+                self.show_code_completion()
                 
     def _key_ctrl_space(self):
         """Action for Ctrl+Space"""
         if not self.is_completion_widget_visible():
-            self.show_code_completion(automatic=False)
+            self.show_code_completion()
                 
     def _key_pageup(self):
         """Action for PageUp key"""
@@ -791,7 +794,7 @@ def _key_period(self, text):
             # Enable auto-completion only if last token isn't a float
             last_obj = self.get_last_obj()
             if last_obj and not last_obj.isdigit():
-                self.show_code_completion(automatic=True)
+                self.show_code_completion()
 
 
     #------ Paste
@@ -819,9 +822,6 @@ def paste(self):
     def get_dir(self, objtxt):
         """Return dir(object)"""
         raise NotImplementedError
-    def get_module_completion(self, objtxt):
-        """Return module completion list associated to object name"""
-        pass
     def get_globals_keys(self):
         """Return shell globals() keys"""
         raise NotImplementedError
@@ -846,41 +846,45 @@ def get_source(self, objtxt):
     def is_defined(self, objtxt, force_import=False):
         """Return True if object is defined"""
         raise NotImplementedError
-        
-    def show_code_completion(self, automatic):
+
+    def show_completion_widget(self, textlist):
+        """Show completion widget"""
+        self.completion_widget.show_list(textlist)
+
+    def hide_completion_widget(self):
+        """Hide completion widget"""
+        self.completion_widget.hide()
+
+    def show_completion_list(self, completions, completion_text=""):
+        """Display the possible completions"""
+        if not completions:
+            return
+        if not isinstance(completions[0], tuple):
+            completions = [(c, '') for c in completions]
+        if len(completions) == 1 and completions[0][0] == completion_text:
+            return
+        self.completion_text = completion_text
+        # Sorting completion list (entries starting with underscore are
+        # put at the end of the list):
+        underscore = set([(comp, t) for (comp, t) in completions
+                          if comp.startswith('_')])
+
+        completions = sorted(set(completions) - underscore,
+                             key=lambda x: str_lower(x[0]))
+        completions += sorted(underscore, key=lambda x: str_lower(x[0]))
+        self.show_completion_widget(completions)
+
+    def show_code_completion(self):
         """Display a completion list based on the current line"""
         # Note: unicode conversion is needed only for ExternalShellBase
         text = to_text_string(self.get_current_line_to_cursor())
         last_obj = self.get_last_obj()
-        
         if not text:
             return
 
-        if text.startswith('import '):
-            obj_list = self.get_module_completion(text)
-            words = text.split(' ')
-            if ',' in words[-1]:
-                words = words[-1].split(',')
-            self.show_completion_list(obj_list, completion_text=words[-1],
-                                      automatic=automatic)
-            return
-            
-        elif text.startswith('from '):
-            obj_list = self.get_module_completion(text)
-            if obj_list is None:
-                return
-            words = text.split(' ')
-            if '(' in words[-1]:
-                words = words[:-2] + words[-1].split('(')
-            if ',' in words[-1]:
-                words = words[:-2] + words[-1].split(',')
-            self.show_completion_list(obj_list, completion_text=words[-1],
-                                      automatic=automatic)
-            return
-        
         obj_dir = self.get_dir(last_obj)
         if last_obj and obj_dir and text.endswith('.'):
-            self.show_completion_list(obj_dir, automatic=automatic)
+            self.show_completion_list(obj_dir)
             return
         
         # Builtins and globals
@@ -889,8 +893,7 @@ def show_code_completion(self, automatic):
             b_k_g = dir(builtins)+self.get_globals_keys()+keyword.kwlist
             for objname in b_k_g:
                 if objname.startswith(last_obj) and objname != last_obj:
-                    self.show_completion_list(b_k_g, completion_text=last_obj,
-                                              automatic=automatic)
+                    self.show_completion_list(b_k_g, completion_text=last_obj)
                     return
             else:
                 return
@@ -908,8 +911,7 @@ def show_code_completion(self, automatic):
             completions = self.get_dir(last_obj)
             if completions is not None:
                 self.show_completion_list(completions,
-                                          completion_text=completion_text,
-                                          automatic=automatic)
+                                          completion_text=completion_text)
                 return
         
         # Looking for ' or ": filename completion
@@ -918,9 +920,15 @@ def show_code_completion(self, automatic):
             completions = self.get_cdlistdir()
             if completions:
                 self.show_completion_list(completions,
-                                          completion_text=text[q_pos+1:],
-                                          automatic=automatic)
+                                          completion_text=text[q_pos+1:])
             return
+
+    def document_did_change(self, text=None):
+        """
+        This is here to be compatible with CodeEditor and be able to use
+        code completion.
+        """
+        pass
             
     #------ Drag'n Drop
     def drop_pathlist(self, pathlist):
diff --git a/spyder/plugins/editor/widgets/base.py b/spyder/plugins/editor/widgets/base.py
--- a/spyder/plugins/editor/widgets/base.py
+++ b/spyder/plugins/editor/widgets/base.py
@@ -31,7 +31,7 @@
 from spyder.config.gui import get_font
 from spyder.config.main import CONF
 from spyder.config.gui import is_dark_interface
-from spyder.py3compat import PY3, str_lower, to_text_string
+from spyder.py3compat import PY3, to_text_string
 from spyder.utils import icon_manager as ima
 from spyder.widgets.calltip import CallTipWidget
 from spyder.widgets.mixins import BaseEditMixin
@@ -81,20 +81,20 @@ def __init__(self, parent, ancestor):
         self.hide()
         self.itemActivated.connect(self.item_selected)
         self.currentRowChanged.connect(self.row_changed)
+        self.is_internal_console = False
 
     def setup_appearance(self, size, font):
         self.resize(*size)
         self.setFont(font)
 
-    def show_list(self, completion_list, automatic=True):
-        # types = [c[1] for c in completion_list]
-        # completion_list = [c[0] for c in completion_list]
-        # if len(completion_list) == 1 and not automatic:
-        #     self.textedit.insert_completion(
-        #         completion_list[0]['insertText'])
-        #     return
+    def show_list(self, completion_list):
+        # Completions are handled differently for the Internal
+        # console.
+        if not isinstance(completion_list[0], dict):
+            self.is_internal_console = True
 
-        self.textedit.completion_text = ''
+        if not self.is_internal_console:
+            self.textedit.completion_text = ''
         self.completion_list = completion_list
         self.clear()
 
@@ -107,14 +107,14 @@ def show_list(self, completion_list, automatic=True):
                      CompletionItemKind.CONSTRUCTOR: 'method',
                      CompletionItemKind.REFERENCE: 'attribute'}
 
-        # self.type_list = types
-        # if any(types):
         for completion in completion_list:
-            icon = icons_map.get(completion['kind'], 'no_match')
-            self.addItem(
-                QListWidgetItem(ima.icon(icon), completion['insertText']))
-        # else:
-            # self.addItems(completion_list)
+            if not self.is_internal_console:
+                icon = icons_map.get(completion['kind'], 'no_match')
+                self.addItem(
+                    QListWidgetItem(ima.icon(icon), completion['insertText']))
+            else:
+                # This is used by the Internal console.
+                self.addItem(QListWidgetItem(completion[0]))
 
         self.setCurrentRow(0)
 
@@ -167,11 +167,12 @@ def show_list(self, completion_list, automatic=True):
             point = ancestor.mapFromGlobal(point)
         self.move(point)
 
-        tooltip_point = QPoint(point)
-        tooltip_point.setX(point.x() + self.width())
-        tooltip_point.setY(point.y() - (3 * self.height()) // 4)
-        for completion in completion_list:
-            completion['point'] = tooltip_point
+        if not self.is_internal_console:
+            tooltip_point = QPoint(point)
+            tooltip_point.setX(point.x() + self.width())
+            tooltip_point.setY(point.y() - (3 * self.height()) // 4)
+            for completion in completion_list:
+                completion['point'] = tooltip_point
 
         if to_text_string(self.textedit.completion_text):
             # When initialized, if completion text is not empty, we need
@@ -223,7 +224,11 @@ def update_current(self):
         completion_text = to_text_string(self.textedit.completion_text)
         if completion_text:
             for row, completion in enumerate(self.completion_list):
-                completion_label = completion['filterText']
+                if not self.is_internal_console:
+                    completion_label = completion['filterText']
+                else:
+                    completion_label = completion[0]
+
                 if not self.case_sensitive:
                     print(completion_text)  # spyder: test-skip
                     completion_label = completion.lower()
@@ -233,8 +238,6 @@ def update_current(self):
                     self.scrollTo(self.currentIndex(),
                                   QAbstractItemView.PositionAtTop)
                     break
-            # if not match:
-                # self.hide()
         else:
             self.hide()
 
@@ -252,8 +255,6 @@ def focusOutEvent(self, event):
     def item_selected(self, item=None):
         if item is None:
             item = self.currentItem()
-        # index = self.currentIndex()
-        # self.textedit.show_calltip()
         self.textedit.insert_completion(to_text_string(item.text()))
         self.hide()
 
@@ -1103,34 +1104,6 @@ def hide_tooltip_if_necessary(self, key):
         except (IndexError, TypeError):
             QToolTip.hideText()
 
-    def show_completion_widget(self, textlist, automatic=True):
-        """Show completion widget"""
-        self.completion_widget.show_list(textlist, automatic=automatic)
-
-    def hide_completion_widget(self):
-        """Hide completion widget"""
-        self.completion_widget.hide()
-
-    def show_completion_list(self, completions, completion_text="",
-                             automatic=True):
-        """Display the possible completions"""
-        if not completions:
-            return
-        if not isinstance(completions[0], tuple):
-            completions = [(c, '') for c in completions]
-        if len(completions) == 1 and completions[0][0] == completion_text:
-            return
-        self.completion_text = completion_text
-        # Sorting completion list (entries starting with underscore are
-        # put at the end of the list):
-        underscore = set([(comp, t) for (comp, t) in completions
-                          if comp.startswith('_')])
-
-        completions = sorted(set(completions) - underscore,
-                             key=lambda x: str_lower(x[0]))
-        completions += sorted(underscore, key=lambda x: str_lower(x[0]))
-        self.show_completion_widget(completions, automatic=automatic)
-
     def select_completion_list(self):
         """Completion list is active, Enter was just pressed"""
         self.completion_widget.item_selected()
@@ -1157,7 +1130,6 @@ def is_completion_widget_visible(self):
         """Return True is completion list widget is visible"""
         return self.completion_widget.isVisible()
 
-
     #------Standard keys
     def stdkey_clear(self):
         if not self.has_selected_text():
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
@@ -789,8 +789,6 @@ def linting_diagnostics(self, params):
     @request(method=LSPRequestTypes.DOCUMENT_COMPLETION)
     def do_completion(self, automatic=False):
         """Trigger completion"""
-        # if self.is_completion_widget_visible():
-        #     return
         self.document_did_change('')
         line, column = self.get_cursor_line_column()
         params = {
@@ -822,7 +820,6 @@ def request_signature(self):
 
     @handles(LSPRequestTypes.DOCUMENT_SIGNATURE)
     def process_signatures(self, params):
-        # self.hide_completion_widget()
         signature = params['params']
         if (signature is not None and
                 'activeParameter' in signature):
@@ -2925,7 +2922,6 @@ def keyPressEvent(self, event):
             self.request_signature()
         elif (text == '(' and
               not self.has_selected_text()):
-            # self.hide_completion_widget()
             self.handle_parentheses(text)
         elif (text in ('[', '{') and not has_selection and
               self.close_parentheses_enabled):
@@ -3026,9 +3022,6 @@ def handle_parentheses(self, text):
             self.insert_text(text)
         if '(' in self.signature_completion_characters:
             self.request_signature()
-        # if self.is_python_like() and self.get_text('sol', 'cursor') and \
-        #         self.calltips:
-        #     self.sig_show_object_info.emit(position)
 
     def mouseMoveEvent(self, event):
         """Underline words when pressing <CONTROL>"""


Test patch: