Repository: spyder-ide/spyder

Issue:
Go to line dialog finds an internal problem
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

1. Open the "Go to line" dialog (Ctrl+L shortcut by default).
2. Type the "+" (plus) sign.

The "Spyder has encountered an internal problem" dialog pops up. On my tests, it appears both with the numpad + and the normal keyboard + keys. Should be a quick fix in the str-to-int procedure.

### Traceback
```python-traceback
  File "C:\Users\u5fs\papps\anaconda3\lib\site-packages\spyder\plugins\editor\widgets\codeeditor.py", line 174, in text_has_changed
    self.lineno = int(text)
ValueError: invalid literal for int() with base 10: '+'
```

## Versions

* Spyder version: 4.1.2 
* Python version: 3.7.3
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Windows 10

### Dependencies

```

# Mandatory:
atomicwrites >=1.2.0           :  1.3.0 (OK)
chardet >=2.0.0                :  3.0.4 (OK)
cloudpickle >=0.5.0            :  1.4.0 (OK)
diff_match_patch >=20181111    :  20181111 (OK)
intervaltree                   :  None (OK)
IPython >=4.0                  :  7.13.0 (OK)
jedi =0.15.2                   :  0.15.2 (OK)
nbconvert >=4.0                :  5.6.1 (OK)
numpydoc >=0.6.0               :  0.9.2 (OK)
paramiko >=2.4.0               :  2.7.1 (OK)
parso =0.5.2                   :  0.5.2 (OK)
pexpect >=4.4.0                :  4.8.0 (OK)
pickleshare >=0.4              :  0.7.5 (OK)
psutil >=5.3                   :  5.7.0 (OK)
pygments >=2.0                 :  2.6.1 (OK)
pylint >=0.25                  :  2.5.0 (OK)
pyls >=0.31.9;<0.32.0          :  0.31.10 (OK)
qdarkstyle >=2.8               :  2.8.1 (OK)
qtawesome >=0.5.7              :  0.7.0 (OK)
qtconsole >=4.6.0              :  4.7.3 (OK)
qtpy >=1.5.0                   :  1.9.0 (OK)
rtree >=0.8.3                  :  0.9.3 (OK)
sphinx >=0.6.6                 :  3.0.3 (OK)
spyder_kernels >=1.9.0;<1.10.0 :  1.9.0 (OK)
watchdog                       :  None (OK)
zmq >=17                       :  18.1.1 (OK)

# Optional:
cython >=0.21                  :  0.29.17 (OK)
matplotlib >=2.0.0             :  3.1.3 (OK)
numpy >=1.7                    :  1.18.1 (OK)
pandas >=0.13.1                :  1.0.3 (OK)
scipy >=0.17.0                 :  1.4.1 (OK)
sympy >=0.7.3                  :  1.5.1 (OK)
```



Discussion:
I can reproduce this

<img width="614" alt="Screen Shot 2020-05-11 at 14 22 08" src="https://user-images.githubusercontent.com/3627835/81602767-cfdb8580-9392-11ea-8f0e-e1aee42cd948.png">

There seems to be an issue with the validator -> https://github.com/spyder-ide/spyder/blob/595f24cccf9c8024635fb14953b19ec9519601d0/spyder/plugins/editor/widgets/codeeditor.py#L133



Resolving patch:
diff --git a/spyder/plugins/editor/widgets/gotoline.py b/spyder/plugins/editor/widgets/gotoline.py
--- a/spyder/plugins/editor/widgets/gotoline.py
+++ b/spyder/plugins/editor/widgets/gotoline.py
@@ -62,8 +62,10 @@ def __init__(self, editor):
 
         ok_button = bbox.button(QDialogButtonBox.Ok)
         ok_button.setEnabled(False)
+        # QIntValidator does not handle '+' sign
+        # See spyder-ide/spyder#20070
         self.lineedit.textChanged.connect(
-                     lambda text: ok_button.setEnabled(len(text) > 0))
+                     lambda text: ok_button.setEnabled(len(text) > 0 and text != '+'))
 
         layout = QHBoxLayout()
         layout.addLayout(glayout)
@@ -75,10 +77,14 @@ def __init__(self, editor):
     def text_has_changed(self, text):
         """Line edit's text has changed."""
         text = str(text)
-        if text:
+
+        # QIntValidator does not handle '+' sign
+        # See spyder-ide/spyder#12693
+        if text and text != '+':
             self.lineno = int(text)
         else:
             self.lineno = None
+            self.lineedit.clear()
 
     def get_line_number(self):
         """Return line number."""
diff --git a/spyder/plugins/variableexplorer/widgets/importwizard.py b/spyder/plugins/variableexplorer/widgets/importwizard.py
--- a/spyder/plugins/variableexplorer/widgets/importwizard.py
+++ b/spyder/plugins/variableexplorer/widgets/importwizard.py
@@ -176,6 +176,8 @@ def __init__(self, parent, text):
         intvalid = QIntValidator(0, len(to_text_string(text).splitlines()),
                                  self.skiprows_edt)
         self.skiprows_edt.setValidator(intvalid)
+        self.skiprows_edt.textChanged.connect(
+                     lambda text: self.get_skiprows())
         other_layout.addWidget(self.skiprows_edt, 0, 1)
 
         other_layout.setColumnMinimumWidth(2, 5)
@@ -236,7 +238,14 @@ def get_row_sep(self):
 
     def get_skiprows(self):
         """Return number of lines to be skipped"""
-        return int(to_text_string(self.skiprows_edt.text()))
+        skip_rows = to_text_string(self.skiprows_edt.text())
+        # QIntValidator does not handle '+' sign
+        # See spyder-ide/spyder#20070
+        if skip_rows and skip_rows != '+':
+            return int(skip_rows)
+        else:
+            self.skiprows_edt.clear()
+            return 0
 
     def get_comments(self):
         """Return comment string"""
@@ -364,12 +373,15 @@ def __init__(self, parent):
     def _shape_text(self, text, colsep=u"\t", rowsep=u"\n",
                     transpose=False, skiprows=0, comments='#'):
         """Decode the shape of the given text"""
-        assert colsep != rowsep
+        assert colsep != rowsep, 'Column sep should not equal Row sep'
         out = []
-        text_rows = text.split(rowsep)[skiprows:]
+        text_rows = text.split(rowsep)
+        assert skiprows < len(text_rows), 'Skip Rows > Line Count'
+        text_rows = text_rows[skiprows:]
         for row in text_rows:
             stripped = to_text_string(row).strip()
-            if len(stripped) == 0 or stripped.startswith(comments):
+            if len(stripped) == 0 or (comments and
+                                      stripped.startswith(comments)):
                 continue
             line = to_text_string(row).split(colsep)
             line = [try_to_parse(to_text_string(x)) for x in line]


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_gotoline.py b/spyder/plugins/editor/widgets/tests/test_gotoline.py
new file mode 100644
--- /dev/null
+++ b/spyder/plugins/editor/widgets/tests/test_gotoline.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""Tests for gotoline.py"""
+
+# Third party imports
+from qtpy.QtWidgets import QDialogButtonBox, QLineEdit
+
+# Local imports
+from spyder.plugins.editor.widgets.gotoline import GoToLineDialog
+
+
+def test_gotolinedialog_has_cancel_button(codeeditor, qtbot, tmpdir):
+    """
+    Test that GoToLineDialog has a Cancel button.
+
+    Test that a GoToLineDialog has a button in a dialog button box and that
+    this button cancels the dialog window.
+    """
+    editor = codeeditor
+    dialog = GoToLineDialog(editor)
+    qtbot.addWidget(dialog)
+    ok_button = dialog.findChild(QDialogButtonBox).button(QDialogButtonBox.Ok)
+    cancel_button = dialog.findChild(QDialogButtonBox).button(QDialogButtonBox.Cancel)
+    assert not ok_button.isEnabled()
+    with qtbot.waitSignal(dialog.rejected):
+        cancel_button.click()
+
+
+def test_gotolinedialog_enter_plus(codeeditor, qtbot):
+    """
+    Regression test for spyder-ide/spyder#12693
+    """
+    editor = codeeditor
+    dialog = GoToLineDialog(editor)
+    qtbot.addWidget(dialog)
+    ok_button = dialog.findChild(QDialogButtonBox).button(QDialogButtonBox.Ok)
+    cancel_button = dialog.findChild(QDialogButtonBox).button(QDialogButtonBox.Cancel)
+    lineedit = dialog.findChild(QLineEdit)
+    lineedit.setText('+')
+
+    # Check + sign being cleared and ok button still is disabled
+    lineedit.setText("+")
+    assert lineedit.text() == ""
+    assert not ok_button.isEnabled()
+
+def test_gotolinedialog_check_valid(codeeditor, qtbot):
+    """
+    Check ok button enabled if valid text entered
+    """
+    editor = codeeditor
+    dialog = GoToLineDialog(editor)
+    qtbot.addWidget(dialog)
+    ok_button = dialog.findChild(QDialogButtonBox).button(QDialogButtonBox.Ok)
+    lineedit = dialog.findChild(QLineEdit)
+    lineedit.setText("1")
+    assert lineedit.text() == "1"
+    assert ok_button.isEnabled()
+    with qtbot.waitSignal(dialog.accepted):
+        ok_button.click()
+    assert dialog.get_line_number() == 1
\ No newline at end of file