Repository: spyder-ide/spyder

Issue:
AttributeError: 'NoneType' object has no attribute 'apply_plugin_settings'
<!--- IMPORTANT: Paste the contents of your clipboard here to complete reporting the problem. --->

<!--- **PLEASE READ:** Before submitting here, please carefully consult our *Troubleshooting Guide*: https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ and search the issues page for your error/problem, as most posted bugs are duplicates or easy fixes.

If you don't find anything, please provide a detailed step-by-step description (in English) of the problem and what led up to it below. Issue reports without a clear way to reproduce them will be closed. Thanks! --->

## Problem Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

Selected Monokai theme and clicked Apply, and then encountered an "internal problem".

### Traceback
```python-traceback
  File "/home/parichay/code/env/spyder3/lib/python3.5/site-packages/spyder/plugins/configdialog.py", line 223, in button_clicked
    configpage.apply_changes()
  File "/home/parichay/code/env/spyder3/lib/python3.5/site-packages/spyder/plugins/configdialog.py", line 104, in apply_changes
    self.apply_callback()
  File "/home/parichay/code/env/spyder3/lib/python3.5/site-packages/spyder/plugins/configdialog.py", line 269, in <lambda>
    self.apply_settings(self.changed_options))
  File "/home/parichay/code/env/spyder3/lib/python3.5/site-packages/spyder/plugins/configdialog.py", line 1187, in apply_settings
    self.main.help.apply_plugin_settings(['color_scheme_name'])
  File "/home/parichay/code/env/spyder3/lib/python3.5/site-packages/spyder/plugins/help.py", line 552, in apply_plugin_settings
    self.editor.apply_plugin_settings(options=[connect_n])
AttributeError: 'NoneType' object has no attribute 'apply_plugin_settings'
```

## Package Versions

* Spyder 3.2.7 ``
* Python 3.5.2
* Qt 5.9.3
* PyQt5 5.9.2
* Linux 4.4.0-112-generic

### 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  :  None (NOK)
numpy >=1.7      :  1.14.1 (OK)
sphinx >=0.6.6   :  1.7.1 (OK)
rope >=0.9.4     :  0.10.7 (OK)
jedi >=0.9.0     :  0.11.1 (OK)
psutil >=0.3     :  5.4.3 (OK)
nbconvert >=4.0  :  5.3.1 (OK)
sympy >=0.7.3    :  None (NOK)
cython >=0.21    :  None (NOK)
qtconsole >=4.2.0:  4.3.1 (OK)
IPython >=4.0    :  6.2.1 (OK)
pylint >=0.25    :  1.8.2 (OK)
```



Discussion:
Is the Editor working for you as expected?
I think I know where this problem is coming from. We'll fix it in our next release.
After the internal error occurs, the whole IDE keeps working as expected (and the new color scheme is applied, too). Spyder does not close in response to the error.
Yeah, the error is not critical but it doesn't allow to apply a new coloring theme, I guess.

Sorry we missed it.
Incorrect in my case: the new syntax highlighting scheme is regularly applied, both in the editor and in the IPython console, despite the error shows up and the error reporting dialog box pops up. In essence, the error does not affect functionality of Spyder on my machine. 

The worst that happens to me is that clicking "Ok" in the Preferences triggers the error but would not close the Preferences box, so I have to click "Apply" (triggering the error **and** effectively applying any changes) and then click "Close" to return to the Editor.
Ok, thanks for letting us know about it. It's simply an annoying thing, still it needs to be fixed.
Hi, I just came back online. Yes, you are right - it lets me change the theme and all, just that the error shows up also and hence I posted it here. It's so nice to see such a good community for Spyder. Thanks for your help.

Resolving patch:
diff --git a/spyder/plugins/help.py b/spyder/plugins/help.py
--- a/spyder/plugins/help.py
+++ b/spyder/plugins/help.py
@@ -338,8 +338,6 @@ def __init__(self, parent=None):
 
         self.internal_shell = None
         self.console = None
-        self.ipyconsole = None
-        self.editor = None
 
         # Initialize plugin
         self.initialize_plugin()
@@ -507,8 +505,6 @@ def register_plugin(self):
 
         self.internal_shell = self.main.console.shell
         self.console = self.main.console
-        self.ipyconsole = self.main.ipyconsole
-        self.editor = self.main.editor
 
     def closing_plugin(self, cancelable=False):
         """Perform actions before parent main window is closed"""
@@ -549,8 +545,8 @@ def apply_plugin_settings(self, options):
             self.toggle_math_mode(math_o)
 
         # To make auto-connection changes take place instantly
-        self.editor.apply_plugin_settings(options=[connect_n])
-        self.ipyconsole.apply_plugin_settings(options=[connect_n])
+        self.main.editor.apply_plugin_settings(options=[connect_n])
+        self.main.ipyconsole.apply_plugin_settings(options=[connect_n])
 
     #------ Public API (related to Help's source) -------------------------
     def source_is_console(self):
@@ -821,8 +817,8 @@ def __eventually_raise_help(self, text, force=False):
                     (force or text != self._last_texts[index])):
                 dockwidgets = self.main.tabifiedDockWidgets(self.dockwidget)
                 if (self.console.dockwidget not in dockwidgets and
-                        self.ipyconsole is not None and
-                        self.ipyconsole.dockwidget not in dockwidgets):
+                        self.main.ipyconsole is not None and
+                        self.main.ipyconsole.dockwidget not in dockwidgets):
                     self.dockwidget.show()
                     self.dockwidget.raise_()
         self._last_texts[index] = text
@@ -904,8 +900,8 @@ def get_shell(self):
                 (hasattr(self.shell, 'is_running') and
                  not self.shell.is_running())):
             self.shell = None
-            if self.ipyconsole is not None:
-                shell = self.ipyconsole.get_current_shellwidget()
+            if self.main.ipyconsole is not None:
+                shell = self.main.ipyconsole.get_current_shellwidget()
                 if shell is not None and shell.kernel_client is not None:
                     self.shell = shell
             if self.shell is None:
@@ -915,8 +911,8 @@ def get_shell(self):
     def render_sphinx_doc(self, doc, context=None):
         """Transform doc string dictionary to HTML and show it"""
         # Math rendering option could have changed
-        if self.editor is not None:
-            fname = self.editor.get_current_filename()
+        if self.main.editor is not None:
+            fname = self.main.editor.get_current_filename()
             dname = osp.dirname(fname)
         else:
             dname = ''


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
@@ -30,6 +30,7 @@
 from qtpy.QtCore import Qt, QTimer, QEvent, QUrl
 from qtpy.QtTest import QTest
 from qtpy.QtWidgets import QApplication, QFileDialog, QLineEdit, QTabBar
+from qtpy.QtWebEngineWidgets import WEBENGINE
 
 # Local imports
 from spyder import __trouble_url__, __project_url__
@@ -40,6 +41,7 @@
 from spyder.plugins import TabFilter
 from spyder.plugins.help import ObjectComboBox
 from spyder.plugins.runconfig import RunConfiguration
+from spyder.plugins.tests.test_help import check_text
 from spyder.py3compat import PY2, to_text_string
 from spyder.utils.ipython.kernelspec import SpyderKernelSpec
 from spyder.utils.programs import is_module_installed
@@ -209,6 +211,52 @@ def test_calltip(main_window, qtbot):
     main_window.editor.close_file()
 
 
+@pytest.mark.slow
+@flaky(max_runs=3)
+@pytest.mark.use_introspection
+def test_get_help(main_window, qtbot):
+    """
+    Test that Help is working when called from the Editor and the
+    IPython console.
+    """
+    shell = main_window.ipyconsole.get_current_shellwidget()
+    control = shell._control
+    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
+
+    help_plugin = main_window.help
+    webview = help_plugin.rich_text.webview._webview
+    if WEBENGINE:
+        webpage = webview.page()
+    else:
+        webpage = webview.page().mainFrame()
+
+    # --- From the console ---
+    # Write some object in the console
+    qtbot.keyClicks(control, 'runfile')
+
+    # Get help
+    control.inspect_current_object()
+
+    # Check that a expected text is part of the page
+    qtbot.waitUntil(lambda: check_text(webpage, "namespace"), timeout=4000)
+
+    # --- From the editor ---
+    qtbot.wait(2000)
+    main_window.editor.new()
+    code_editor = main_window.editor.get_focus_widget()
+    editorstack = main_window.editor.get_current_editorstack()
+
+    # Write some object in the editor
+    code_editor.set_text('range')
+    code_editor.move_cursor(len('range'))
+
+    # Get help
+    editorstack.inspect_current_object()
+
+    # Check that a expected text is part of the page
+    qtbot.waitUntil(lambda: check_text(webpage, "range"), timeout=4000)
+
+
 @pytest.mark.slow
 def test_window_title(main_window, tmpdir):
     """Test window title with non-ascii characters."""
diff --git a/spyder/plugins/tests/test_help.py b/spyder/plugins/tests/test_help.py
--- a/spyder/plugins/tests/test_help.py
+++ b/spyder/plugins/tests/test_help.py
@@ -17,6 +17,7 @@
     from mock import Mock, MagicMock  # Python 2
 
 # Third party imports
+from qtpy.QtWidgets import QWidget
 from qtpy.QtWebEngineWidgets import WEBENGINE
 import pytest
 from flaky import flaky
@@ -32,9 +33,17 @@
 @pytest.fixture
 def help_plugin(qtbot):
     """Help plugin fixture"""
-    help_plugin = Help()
-    webview = help_plugin.rich_text.webview._webview
 
+    class MainMock(QWidget):
+        def __getattr__(self, attr):
+            if attr == 'ipyconsole' or attr == 'editor':
+                return None
+            else:
+                return Mock()
+
+    help_plugin = Help(parent=MainMock())
+
+    webview = help_plugin.rich_text.webview._webview
     if WEBENGINE:
         help_plugin._webpage = webview.page()
     else:
@@ -93,7 +102,7 @@ def test_no_further_docs_message(help_plugin, qtbot):
                     timeout=3000)
 
 
-def test_help_opens_when_show_tutorial_unit(help_plugin, qtbot,):
+def test_help_opens_when_show_tutorial_unit(help_plugin, qtbot):
     """Test fix for #6317 : 'Show tutorial' opens the help plugin if closed."""
     MockDockwidget = MagicMock()
     MockDockwidget.return_value.isVisible.return_value = False