Repository: spyder-ide/spyder

Issue:
IPython console crashes when using netCDF4
## Problem Description
Spyder 4.0.0b3 crashes when loading and using netCDF4 package within the iPython Console. Does not happen with the editor.


### What steps reproduce the problem?
1. import netcdf4
2. Only in the iPython console -> when typing "netcdf4.Dataset( " the exact same moment as you type the first bracket the IDE crashes and the ubuntu crashes with it.


### What is the expected output? What do you see instead?
Instead of opening the function description window the whole thing crashes and takes the ubuntu OS with it.

--- Note ---
I'm running spyder4.0.0b3 via x11 forwarding from our dept. server. Previous versions of Spyder always worked perfectly and smoothly with zero errors.

My local machine runs Ubuntu 18.04.2 LTS
The server's machine is 	CentOS Linux release 7.4.1708


## Versions
* Spyder version: Spyder 4.0.0b3 
* Python version: Python 3.7.3 64-bit
* Qt version: Qt 5.9.7 
* PyQt version: PyQt5 5.9.2 
* Operating System nQt 5.9.7 ame/version: Linux 3.10.0-693.5.2.el7.x86_64 


### Dependencies
```
pygments >=2.0    :  2.4.2 (OK)
qdarkstyle >=2.6.4:  2.7 (OK)
sphinx >=0.6.6    :  2.1.0 (OK)
psutil >=0.3      :  5.6.2 (OK)
pyls >=0.27.0     :  0.27.0 (OK)
nbconvert >=4.0   :  5.5.0 (OK)
pandas >=0.13.1   :  0.24.2 (OK)
numpy >=1.7       :  1.16.4 (OK)
sympy >=0.7.3     :  1.4 (OK)
cython >=0.21     :  0.29.13 (OK)
qtconsole >=4.5.0 :  4.5.1 (OK)
IPython >=4.0     :  7.5.0 (OK)
matplotlib >=2.0.0:  3.1.0 (OK)
pylint >=0.25     :  2.3.1 (OK)
```



Discussion:
Thanks for reporting. How did you install Spyder and netcdf4?
Thanks for the quick reply!
I installed Spyder as such;
conda update qt pyqt
conda install -c spyder-ide spyder=4.0.0b3
(as instructed here - https://github.com/spyder-ide/spyder/releases/tag/v4.0.0b3)


and as for the netCDF4

conda install netcdf4


Stav
We're you able to reproduce the error?
We'll take a look at it for beta5 because beta4 (to be released this week) is almost ready.
@dalthviz, please take a look at this one to see if you can reproduce it.
Cheers, I'll keep following the thread and if anything comes up I'll keep u updated
thanks for the hard work!



Resolving patch:
diff --git a/spyder/widgets/mixins.py b/spyder/widgets/mixins.py
--- a/spyder/widgets/mixins.py
+++ b/spyder/widgets/mixins.py
@@ -205,19 +205,34 @@ def _format_text(self, title=None, signature=None, text=None,
             text = text.strip()
 
         if not with_html_format:
-            paragraphs = text.splitlines()
+            # All these replacements are need to properly divide the
+            # text in actual paragraphs and wrap the text on each one
+            paragraphs = (text
+                          .replace("\n\n", "<!DOUBLE_ENTER!>")
+                          .replace(".\n", ".<!SINGLE_ENTER!>")
+                          .replace("\n-", "<!SINGLE_ENTER!>-")
+                          .replace("-\n", "-<!SINGLE_ENTER!>")
+                          .replace("\n ", "<!SINGLE_ENTER!> ")
+                          .replace(" \n", " <!SINGLE_ENTER!>")
+                          .replace("\n", " ")
+                          .replace("<!DOUBLE_ENTER!>", "\n\n")
+                          .replace("<!SINGLE_ENTER!>", "\n").splitlines())
+            new_paragraphs = []
             for paragraph in paragraphs:
                 # Wrap text
-                paragraph = textwrap.wrap(text, width=max_width)
+                new_paragraph = textwrap.wrap(paragraph, width=max_width)
 
                 # Remove empty lines at the beginning
-                paragraph = [l for l in paragraph if l.strip()]
+                new_paragraph = [l for l in new_paragraph if l.strip()]
 
                 # Merge paragraph text
-                paragraph = '\n'.join(paragraph)
+                new_paragraph = '\n'.join(new_paragraph)
+
+                # Add new paragraph
+                new_paragraphs.append(new_paragraph)
 
             # Join paragraphs and split in lines for max_lines check
-            paragraphs = '\n'.join(paragraphs)
+            paragraphs = '\n'.join(new_paragraphs)
             paragraphs = paragraphs.strip('\r\n')
             lines = paragraphs.splitlines()
 
@@ -477,7 +492,7 @@ def show_calltip(self, signature, parameter=None, documentation=None,
         point = self._calculate_position()
 
         language = getattr(self, 'language', language).lower()
-        if language == 'python':
+        if language == 'python' and signature.strip():
             # Check if documentation is better than signature, sometimes
             # signature has \n stripped for functions like print, type etc
             check_doc = ' '
@@ -491,7 +506,8 @@ def show_calltip(self, signature, parameter=None, documentation=None,
         # Remove duplicate signature inside documentation
         if documentation:
             documentation = documentation.replace('\\*', '*')
-            documentation = documentation.replace(signature + '\n', '')
+            if signature.strip():
+                documentation = documentation.replace(signature + '\n', '')
 
         # Format
         res = self._check_signature_and_format(signature, parameter,


Test patch:
diff --git a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
--- a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
+++ b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
@@ -169,19 +169,19 @@ def close_console():
 @pytest.mark.slow
 @flaky(max_runs=3)
 @pytest.mark.parametrize(
-        "function,signature,documentation",
-        [("arange",
-          ["start", "stop"],
-          ["Return evenly spaced values within a given interval.<br>",
-           "returns an ndarray rather than a list.<br>"]),
-         ("vectorize",
-          ["pyfunc", "otype", "signature"],
-          ["Generalized function class.<br>",
-           "numpy array or a tuple of numpy ..."]),
-         ("absolute",
-          ["x", "/", "out"],
-          ["Parameters<br>", "x : array_like ..."])]
-        )
+    "function,signature,documentation",
+    [("arange",
+      ["start", "stop"],
+      ["Return evenly spaced values within a given interval.<br>",
+       "<br>Python built-in `range` function, but returns an ndarray ..."]),
+     ("vectorize",
+      ["pyfunc", "otype", "signature"],
+      ["Generalized function class.<br>",
+       "Define a vectorized function which takes a nested sequence ..."]),
+     ("absolute",
+      ["x", "/", "out"],
+      ["Parameters<br>", "x : array_like ..."])]
+    )
 @pytest.mark.skipif(sys.platform == 'darwin', reason="Times out on macOS")
 def test_get_calltips(ipyconsole, qtbot, function, signature, documentation):
     """Test that calltips show the documentation."""