Repository: spyder-ide/spyder

Issue:
Rename in (variable explorer) is not working properly
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

### Issue Report Checklist

* [x] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [x] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [ ] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [ ] Tried basic troubleshooting (if a bug/error)
    * [x] Restarted Spyder
    * [x] Reset preferences with ``spyder --reset``
    * [ ] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [ ] Tried the other applicable steps from the Troubleshooting Guide
* [x] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description

check GIF below

![rename2](https://user-images.githubusercontent.com/22060413/68988863-a1537680-0857-11ea-98ff-b99973f7d1a6.gif)

Possibly similar to [#10709](https://github.com/spyder-ide/spyder/issues/10709)

```python-traceback

No traceback

```

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: 4rc1 Commit: [c8ea0afa0](https://github.com/spyder-ide/spyder/commit/c8ea0afa0)
* Python version: 3.7
* Qt version: 
* PyQt version: 
* Operating System name/version: windows 10

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
cloudpickle >=0.5.0          :  1.2.2 (OK)
pygments >=2.0               :  2.4.2 (OK)
qtconsole >=4.5.5            :  4.5.5 (OK)
nbconvert >=4.0              :  5.6.1 (OK)
sphinx >=0.6.6               :  2.2.1 (OK)
pylint >=0.25                :  2.4.3 (OK)
psutil >=0.3                 :  5.6.5 (OK)
qtawesome >=0.5.7            :  0.6.0 (OK)
qtpy >=1.5.0                 :  1.9.0 (OK)
pickleshare >=0.4            :  0.7.5 (OK)
zmq >=17                     :  18.1.0 (OK)
chardet >=2.0.0              :  3.0.4 (OK)
numpydoc >=0.6.0             :  0.9.1 (OK)
spyder_kernels >=1.7.0;<2.0.0:  1.7.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.13 (OK)
IPython >=4.0                :  7.9.0 (OK)
matplotlib >=2.0.0           :  3.1.1 (OK)
pandas >=0.13.1              :  0.25.2 (OK)
numpy >=1.7                  :  1.17.3 (OK)
scipy >=0.17.0               :  1.3.1 (OK)
pyls >=0.29.3;<0.30.0        :  0.29.3 (OK)
rtree >=0.8.3                :  0.8.3 (OK)
```



Discussion:
@dalthviz, please take a look at this one.

Resolving patch:
diff --git a/spyder/plugins/variableexplorer/widgets/collectionseditor.py b/spyder/plugins/variableexplorer/widgets/collectionseditor.py
--- a/spyder/plugins/variableexplorer/widgets/collectionseditor.py
+++ b/spyder/plugins/variableexplorer/widgets/collectionseditor.py
@@ -879,12 +879,13 @@ def remove_item(self, force=False):
             keys = [self.source_model.keys[idx_row] for idx_row in idx_rows]
             self.remove_values(keys)
 
-    def copy_item(self, erase_original=False):
+    def copy_item(self, erase_original=False, new_name=None):
         """Copy item"""
         indexes = self.selectedIndexes()
         if not indexes:
             return
-        idx_rows = unsorted_unique([idx.row() for idx in indexes])
+        idx_rows = unsorted_unique(
+            [self.proxy_model.mapToSource(idx).row() for idx in indexes])
         if len(idx_rows) > 1 or not indexes[0].isValid():
             return
         orig_key = self.source_model.keys[idx_rows[0]]
@@ -897,6 +898,8 @@ def copy_item(self, erase_original=False):
         data = self.source_model.get_data()
         if isinstance(data, (list, set)):
             new_key, valid = len(data), True
+        elif new_name is not None:
+            new_key, valid = new_name, True
         else:
             new_key, valid = QInputDialog.getText(self, title, field_text,
                                                   QLineEdit.Normal, orig_key)
@@ -914,9 +917,9 @@ def duplicate_item(self):
         self.copy_item()
 
     @Slot()
-    def rename_item(self):
+    def rename_item(self, new_name=None):
         """Rename item"""
-        self.copy_item(True)
+        self.copy_item(erase_original=True, new_name=new_name)
 
     @Slot()
     def insert_item(self):
@@ -925,7 +928,7 @@ def insert_item(self):
         if not index.isValid():
             row = self.source_model.rowCount()
         else:
-            row = index.row()
+            row = self.proxy_model.mapToSource(index).row()
         data = self.source_model.get_data()
         if isinstance(data, list):
             key = row
@@ -973,7 +976,8 @@ def plot_item(self, funcname):
         """Plot item"""
         index = self.currentIndex()
         if self.__prepare_plot():
-            key = self.source_model.get_key(index)
+            key = self.source_model.get_key(
+                self.proxy_model.mapToSource(index))
             try:
                 self.plot(key, funcname)
             except (ValueError, TypeError) as error:
@@ -987,7 +991,8 @@ def imshow_item(self):
         """Imshow item"""
         index = self.currentIndex()
         if self.__prepare_plot():
-            key = self.source_model.get_key(index)
+            key = self.source_model.get_key(
+                self.proxy_model.mapToSource(index))
             try:
                 if self.is_image(key):
                     self.show_image(key)


Test patch:
diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py
--- a/spyder/app/tests/test_mainwindow.py
+++ b/spyder/app/tests/test_mainwindow.py
@@ -2428,6 +2428,72 @@ def test_runcell(main_window, qtbot, tmpdir, debug):
         pass
 
 
+@pytest.mark.slow
+@flaky(max_runs=3)
+def test_varexp_rename(main_window, qtbot, tmpdir):
+    """
+    Test renaming a variable.
+    Regression test for spyder-ide/spyder#10735
+    """
+    # ---- Setup ----
+    p = (tmpdir.mkdir(u"varexp_rename").join(u"script.py"))
+    filepath = to_text_string(p)
+    shutil.copyfile(osp.join(LOCATION, 'script.py'), filepath)
+
+    # Wait until the window is fully up
+    shell = main_window.ipyconsole.get_current_shellwidget()
+    qtbot.waitUntil(lambda: shell._prompt_html is not None,
+                    timeout=SHELL_TIMEOUT)
+
+    # Load test file
+    main_window.editor.load(filepath)
+
+    # Move to the editor's first line
+    code_editor = main_window.editor.get_focus_widget()
+    code_editor.setFocus()
+    qtbot.keyClick(code_editor, Qt.Key_Home, modifier=Qt.ControlModifier)
+
+    # Get a reference to the namespace browser widget
+    nsb = main_window.variableexplorer.get_focus_widget()
+
+    # ---- Run file ----
+    with qtbot.waitSignal(shell.executed):
+        qtbot.keyClick(code_editor, Qt.Key_F5)
+
+    # Wait until all objects have appeared in the variable explorer
+    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 4,
+                    timeout=EVAL_TIMEOUT)
+
+    # Rename one element
+    nsb.editor.setCurrentIndex(nsb.editor.model.index(1, 0))
+    nsb.editor.rename_item(new_name='arr2')
+
+    # Wait until all objects have updated in the variable explorer
+    def data(cm, i, j):
+        return cm.data(cm.index(i, j))
+    qtbot.waitUntil(lambda: data(nsb.editor.model, 1, 0) == 'arr2',
+                    timeout=EVAL_TIMEOUT)
+
+    assert data(nsb.editor.model, 0, 0) == 'a'
+    assert data(nsb.editor.model, 1, 0) == 'arr2'
+    assert data(nsb.editor.model, 2, 0) == 'li'
+    assert data(nsb.editor.model, 3, 0) == 's'
+
+    # ---- Run file again ----
+    with qtbot.waitSignal(shell.executed):
+        qtbot.keyClick(code_editor, Qt.Key_F5)
+
+    # Wait until all objects have appeared in the variable explorer
+    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 5,
+                    timeout=EVAL_TIMEOUT)
+
+    assert data(nsb.editor.model, 0, 0) == 'a'
+    assert data(nsb.editor.model, 1, 0) == 'arr'
+    assert data(nsb.editor.model, 2, 0) == 'arr2'
+    assert data(nsb.editor.model, 3, 0) == 'li'
+    assert data(nsb.editor.model, 4, 0) == 's'
+
+
 @pytest.mark.slow
 @flaky(max_runs=3)
 def test_varexp_remove(main_window, qtbot, tmpdir):
diff --git a/spyder/plugins/variableexplorer/widgets/tests/test_collectioneditor.py b/spyder/plugins/variableexplorer/widgets/tests/test_collectioneditor.py
--- a/spyder/plugins/variableexplorer/widgets/tests/test_collectioneditor.py
+++ b/spyder/plugins/variableexplorer/widgets/tests/test_collectioneditor.py
@@ -72,6 +72,45 @@ def nonsettable_objects_data():
 # =============================================================================
 # Tests
 # ============================================================================
+def test_rename_variable(qtbot):
+    """Test renaming of the correct variable."""
+    variables = {'a': 1,
+                 'b': 2,
+                 'c': 3,
+                 'd': '4',
+                 'e': 5}
+    editor = CollectionsEditorTableView(None, variables.copy())
+    qtbot.addWidget(editor)
+    editor.setCurrentIndex(editor.model.index(1, 0))
+
+    editor.rename_item(new_name='b2')
+    assert editor.model.rowCount() == 5
+    assert data(editor.model, 0, 0) == 'a'
+    assert data(editor.model, 1, 0) == 'b2'
+    assert data(editor.model, 2, 0) == 'c'
+    assert data(editor.model, 3, 0) == 'd'
+    assert data(editor.model, 4, 0) == 'e'
+
+    # Reset variables and try renaming one again
+    new_variables = {'a': 1,
+                     'b': 2,
+                     'b2': 2,
+                     'c': 3,
+                     'd': '4',
+                     'e': 5}
+    editor.set_data(new_variables.copy())
+    editor.adjust_columns()
+    editor.setCurrentIndex(editor.model.index(1, 0))
+    editor.rename_item(new_name='b3')
+    assert editor.model.rowCount() == 6
+    assert data(editor.model, 0, 0) == 'a'
+    assert data(editor.model, 1, 0) == 'b2'
+    assert data(editor.model, 2, 0) == 'b3'
+    assert data(editor.model, 3, 0) == 'c'
+    assert data(editor.model, 4, 0) == 'd'
+    assert data(editor.model, 5, 0) == 'e'
+
+
 def test_remove_variable(qtbot):
     """Test removing of the correct variable."""
     variables = {'a': 1,
@@ -362,7 +401,7 @@ def test_rename_and_duplicate_item_in_collection_editor():
         editor = CollectionsEditorTableView(None, coll)
         assert editor.rename_action.isEnabled()
         assert editor.duplicate_action.isEnabled()
-        editor.setCurrentIndex(editor.source_model.index(0, 0))
+        editor.setCurrentIndex(editor.model.index(0, 0))
         editor.refresh_menu()
         assert editor.rename_action.isEnabled() == rename_enabled
         assert editor.duplicate_action.isEnabled() == duplicate_enabled