Repository: spyder-ide/spyder

Issue:
Error when choosing rename option after editing tuple value from Variable explorer.
## Description

**What steps will reproduce the problem?**

1.  
tup = 123,'abc', 235, (2,3,5)
tup = tup + ('a', 'b')
2.
From Variable explorer - Edit the tup variable. 
3. 
Choose Rename and it will thru an error

**What is the expected output? What do you see instead?**


**Please provide any additional information below**

  File "C:\Users\S381753\Python\Anaconda3\lib\site-packages\spyder\widgets\variableexplorer\collectionseditor.py", line 972, in rename_item
    self.copy_item(True)
  File "C:\Users\S381753\Python\Anaconda3\lib\site-packages\spyder\widgets\variableexplorer\collectionseditor.py", line 955, in copy_item
    QLineEdit.Normal, orig_key)
TypeError: getText(QWidget, str, str, echo: QLineEdit.EchoMode = QLineEdit.Normal, text: str = '', flags: Union[Qt.WindowFlags, Qt.WindowType] = Qt.WindowFlags(), inputMethodHints: Union[Qt.InputMethodHints, Qt.InputMethodHint] = Qt.ImhNone): argument 5 has unexpected type 'int'


## Version and main components

* Spyder Version: 3.2.4 
* Python Version: 3.6.3
* Qt Versions: 5.6.2, PyQt5 5.6 on Windows

## Dependencies
```
pyflakes >=0.6.0 :  1.6.0 (OK)
pycodestyle >=2.3:  2.3.1 (OK)
pygments >=2.0   :  2.2.0 (OK)
pandas >=0.13.1  :  0.20.3 (OK)
numpy >=1.7      :  1.13.3 (OK)
sphinx >=0.6.6   :  1.6.3 (OK)
rope >=0.9.4     :  0.10.5 (OK)
jedi >=0.9.0     :  0.10.2 (OK)
nbconvert >=4.0  :  5.3.1 (OK)
sympy >=0.7.3    :  1.1.1 (OK)
cython >=0.21    :  0.26.1


Discussion:
Thanks for reporting. I've reproduced the apparent bug, minimized even further:

1. Create a built in Python object with more than one element that can be opened in a separate edit window in Variable explorer, confirmed on a list and a tuple (possibly others)
2. Doubleclick/edit "foobar" in Variable explorer. 
3. Then, right click anywhere, on anything, and select "Rename" or "Duplicate", and the above error occurs.

However, renaming etc works fine on any of those objects when done on the parent object, from the main variable explorer window, which I assume is what you are intending to do, so at least until this is fixed you can simply do that with no loss in actual functionality.

Therefore, it seems that either "Rename" and "Duplicate" should be disabled in the contextmenu when within variable explorer sub windows, as those commands do not (and, likely cannot) function properly on the sub elements (i.e. indicies) of most parent objects, or within that window they should still act on the parent object (more functional, but perhaps more confusing/inconsistent). @ccordoba12 would be able to respond more authoritatively to this.
@ccordoba12 renaming within a collection editor offers the user the possibility to break the data model: for example within the editor of `l = [1, 2]` I could choose to rename the last row into, say, 99. But for a `list `the model is stored as a `list `(!) and the keys are the indexes. Renaming means adding new keys. In my previous example it brings a question: 

- should we validate that the input key is ok (so nok in my example)?
- should we not show the `rename` option for lists?

@Prikers Out of curiosity, what would be the practical use of doing so, given the limitations you mention? Also, probably restating the obvious, but the latter choice (disabling the rename and duplicate options) would presumably be what should happen for tuples regardless.

Also, on a tangentially related note, mucking around with a tuple in the Variable Explorer actually lets me edit (rather than just select, copy etc) its values when the text box is active, but the changes don't stick (of course). Presumably, the text should be selectable, but not editable, to match the immutability of the data type—I would imagine this would just be some property of the textbox Qt object that needs to be set, or something like that?
> should we not show the rename option for lists?

I think that's the simplest option, yep.
> Presumably, the text should be selectable, but not editable, to match the immutability of the data type

Right, I agree with this one too.

Resolving patch:
diff --git a/spyder/widgets/variableexplorer/collectionseditor.py b/spyder/widgets/variableexplorer/collectionseditor.py
--- a/spyder/widgets/variableexplorer/collectionseditor.py
+++ b/spyder/widgets/variableexplorer/collectionseditor.py
@@ -951,8 +951,12 @@ def copy_item(self, erase_original=False):
         else:
             title = _('Duplicate')
             field_text = _('Variable name:')
-        new_key, valid = QInputDialog.getText(self, title, field_text,
-                                              QLineEdit.Normal, orig_key)
+        data = self.model.get_data()
+        if isinstance(data, (list, set)):
+            new_key, valid = len(data), True
+        else:
+            new_key, valid = QInputDialog.getText(self, title, field_text,
+                                                  QLineEdit.Normal, orig_key)
         if valid and to_text_string(new_key):
             new_key = try_to_eval(to_text_string(new_key))
             if new_key == orig_key:
@@ -1161,7 +1165,12 @@ def remove_values(self, keys):
     def copy_value(self, orig_key, new_key):
         """Copy value"""
         data = self.model.get_data()
-        data[new_key] = data[orig_key]
+        if isinstance(data, list):
+            data.append(data[orig_key])
+        if isinstance(data, set):
+            data.add(data[orig_key])
+        else:
+            data[new_key] = data[orig_key]
         self.set_data(data)
     
     def new_value(self, key, value):
@@ -1242,6 +1251,9 @@ def refresh_menu(self):
         self.edit_action.setEnabled( condition )
         self.remove_action.setEnabled( condition )
         self.insert_action.setEnabled( not self.readonly )
+        self.duplicate_action.setEnabled(condition)
+        condition_rename = not isinstance(data, (tuple, list, set))
+        self.rename_action.setEnabled(condition_rename)
         self.refresh_plot_entries(index)
         
     def set_filter(self, dictfilter=None):


Test patch:
diff --git a/spyder/widgets/variableexplorer/tests/test_collectioneditor.py b/spyder/widgets/variableexplorer/tests/test_collectioneditor.py
--- a/spyder/widgets/variableexplorer/tests/test_collectioneditor.py
+++ b/spyder/widgets/variableexplorer/tests/test_collectioneditor.py
@@ -8,6 +8,7 @@
 """
 
 # Standard library imports
+import copy
 try:
     from unittest.mock import Mock
 except ImportError:
@@ -142,5 +143,23 @@ def test_sort_collectionsmodel_with_many_rows():
     assert cm.rowCount() == len(coll)
 
 
+def test_rename_and_duplicate_item_in_collection_editor():
+    collections = {'list': ([1, 2, 3], False, True),
+                   'tuple': ((1, 2, 3), False, False),
+                   'dict': ({'a': 1, 'b': 2}, True, True)}
+    for coll, rename_enabled, duplicate_enabled in collections.values():
+        coll_copy = copy.copy(coll)
+        editor = CollectionsEditorTableView(None, coll)
+        assert editor.rename_action.isEnabled()
+        assert editor.duplicate_action.isEnabled()
+        editor.setCurrentIndex(editor.model.createIndex(0, 0))
+        editor.refresh_menu()
+        assert editor.rename_action.isEnabled() == rename_enabled
+        assert editor.duplicate_action.isEnabled() == duplicate_enabled
+        if isinstance(coll, list):
+            editor.duplicate_item()
+            assert editor.model.get_data() == coll_copy + [coll_copy[0]]
+
+
 if __name__ == "__main__":
     pytest.main()