Repository: spyder-ide/spyder

Issue:
Variable explorer raise error when try to sort dict by values
## Description

**What steps will reproduce the problem?**

1. open a dict in variable explorer
2. try to sort dict by values
3. 

**What is the expected output? What do you see instead?**
i want a sorted dict,but change nothing,then raise this error

**Please provide any additional information below**

  File "/home/shanjie/miniconda3/lib/python3.6/site-packages/spyder/widgets/variableexplorer/collectionseditor.py", line 250, in fetchMore
    self.rows_loaded + items_to_fetch)
  File "/home/shanjie/miniconda3/lib/python3.6/site-packages/spyder/widgets/variableexplorer/collectionseditor.py", line 179, in set_size_and_type
    for index in range(start, stop) ]
  File "/home/shanjie/miniconda3/lib/python3.6/site-packages/spyder/widgets/variableexplorer/collectionseditor.py", line 179, in <listcomp>
    for index in range(start, stop) ]
IndexError: list index out of range


## Version and main components

* Spyder Version: 3.2.1 
* Python Version: 3.6.1
* Qt Versions: 5.6.2, PyQt5 5.6 on Linux

## Dependencies
```
pyflakes >=0.6.0 :  1.5.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.1 (OK)
sphinx >=0.6.6   :  1.6.2 (OK)
rope >=0.9.4     :  0.9.4-1 (OK)
jedi >=0.9.0     :  0.10.2 (OK)
psutil >=0.3     :  5.2.2 (OK)
nbconvert >=4.0  :  5.2.1 (OK)
sympy >=0.7.3    :  1.1.1 (OK)
cython >=0.21    :  0.26 (OK)
qtconsole >=4.2.0:  4.3.0 (OK)
IPython >=4.0    :  6.1.0 (OK)
pylint >=0.25    :  1.6.4 (OK)

```



Discussion:
Thanks for reporting. We'll fix this in a future release.
@dalthviz, please take a look at this one.

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
@@ -60,7 +60,7 @@
 
 
 LARGE_NROWS = 100
-
+ROWS_TO_LOAD = 50
 
 class ProxyObject(object):
     """Dictionary proxy to an unknown object."""
@@ -87,7 +87,6 @@ def __setitem__(self, key, value):
 
 class ReadOnlyCollectionsModel(QAbstractTableModel):
     """CollectionsEditor Read-Only Table Model"""
-    ROWS_TO_LOAD = 50
 
     def __init__(self, parent, data, title="", names=False,
                  minmax=False, dataframe_format=None, remote=False):
@@ -152,7 +151,7 @@ def set_data(self, data, coll_filter=None):
 
         self.total_rows = len(self.keys)
         if self.total_rows > LARGE_NROWS:
-            self.rows_loaded = self.ROWS_TO_LOAD
+            self.rows_loaded = ROWS_TO_LOAD
         else:
             self.rows_loaded = self.total_rows
 
@@ -198,27 +197,22 @@ def sort(self, column, order=Qt.AscendingOrder):
             except:
                 pass
         elif column == 1:
-            self.keys = sort_against(self.keys, self.types, reverse)
+            self.keys[:self.rows_loaded] = sort_against(self.keys, self.types,
+                                                        reverse)
             self.sizes = sort_against(self.sizes, self.types, reverse)
             try:
                 self.types.sort(reverse=reverse)
             except:
                 pass
         elif column == 2:
-            self.keys = sort_against(self.keys, self.sizes, reverse)
+            self.keys[:self.rows_loaded] = sort_against(self.keys, self.sizes,
+                                                        reverse)
             self.types = sort_against(self.types, self.sizes, reverse)
             try:
                 self.sizes.sort(reverse=reverse)
             except:
                 pass
         elif column == 3:
-            self.keys = sort_against(self.keys, self.sizes, reverse)
-            self.types = sort_against(self.types, self.sizes, reverse)
-            try:
-                self.sizes.sort(reverse=reverse)
-            except:
-                pass
-        elif column == 4:
             values = [self._data[key] for key in self.keys]
             self.keys = sort_against(self.keys, values, reverse)
             self.sizes = sort_against(self.sizes, values, reverse)
@@ -245,7 +239,7 @@ def canFetchMore(self, index=QModelIndex()):
  
     def fetchMore(self, index=QModelIndex()):
         reminder = self.total_rows - self.rows_loaded
-        items_to_fetch = min(reminder, self.ROWS_TO_LOAD)
+        items_to_fetch = min(reminder, ROWS_TO_LOAD)
         self.set_size_and_type(self.rows_loaded,
                                self.rows_loaded + items_to_fetch)
         self.beginInsertRows(QModelIndex(), self.rows_loaded,


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
@@ -19,12 +19,15 @@
 
 # Local imports
 from spyder.widgets.variableexplorer.collectionseditor import (
-    CollectionsEditorTableView, CollectionsModel)
+    CollectionsEditorTableView, CollectionsModel, LARGE_NROWS, ROWS_TO_LOAD)
 
 # Helper functions
 def data(cm, i, j):
     return cm.data(cm.createIndex(i, j))
 
+def data_table(cm, n_rows, n_cols):
+    return [[data(cm, i, j) for i in range(n_rows)] for j in range(n_cols)]
+
 # --- Tests
 # -----------------------------------------------------------------------------
 
@@ -95,5 +98,49 @@ def test_shows_dataframeeditor_when_editing_datetimeindex(qtbot, monkeypatch):
     mockDataFrameEditor_instance.show.assert_called_once_with()
 
 
+def test_sort_collectionsmodel():
+    coll = [1, 3, 2]
+    cm = CollectionsModel(None, coll)
+    assert cm.rowCount() == 3
+    assert cm.columnCount() == 4
+    cm.sort(0)  # sort by index
+    assert data_table(cm, 3, 4) == [['0', '1', '2'],
+                                    ['int', 'int', 'int'],
+                                    ['1', '1', '1'],
+                                    ['1', '3', '2']]
+    cm.sort(3)  # sort by value
+    assert data_table(cm, 3, 4) == [['0', '2', '1'],
+                                    ['int', 'int', 'int'],
+                                    ['1', '1', '1'],
+                                    ['1', '2', '3']]
+    coll = [[1, 2], 3]
+    cm = CollectionsModel(None, coll)
+    assert cm.rowCount() == 2
+    assert cm.columnCount() == 4
+    cm.sort(1)  # sort by type
+    assert data_table(cm, 2, 4) == [['1', '0'],
+                                    ['int', 'list'],
+                                    ['1', '2'],
+                                    ['3', '[1, 2]']]
+    cm.sort(2)  # sort by size
+    assert data_table(cm, 2, 4) == [['1', '0'],
+                                    ['int', 'list'],
+                                    ['1', '2'],
+                                    ['3', '[1, 2]']]
+
+
+def test_sort_collectionsmodel_with_many_rows():
+    coll = list(range(2*LARGE_NROWS))
+    cm = CollectionsModel(None, coll)
+    assert cm.rowCount() == cm.rows_loaded == ROWS_TO_LOAD
+    assert cm.columnCount() == 4
+    cm.sort(1)  # This was causing an issue (#5232)
+    cm.fetchMore()
+    assert cm.rowCount() == 2 * ROWS_TO_LOAD
+    for _ in range(3):
+        cm.fetchMore()
+    assert cm.rowCount() == len(coll)
+
+
 if __name__ == "__main__":
     pytest.main()