Repository: spyder-ide/spyder

Issue:
Error when deleting a project
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

## Problem Description

Deleting the currently open project results in an error.

### What steps reproduce the problem?

1. Delete a project from the menu `Project ->Delete Project`

### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python-traceback
  File "C:\Users\User\spyder\spyder\plugins\projects\plugin.py", line 338, in _delete_project
    self.explorer.delete_project()
  File "C:\Users\User\spyder\spyder\plugins\projects\widgets\explorer.py", line 239, in delete_project
    if self.current_active_project:
AttributeError: 'ProjectExplorerWidget' object has no attribute 'current_active_project'
```

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: Spyder 4.0.0.dev0 https://github.com/spyder-ide/spyder/commit/4d1614c3744be42e315704c0d663d606a836a411
* Python version: Python 3.6.6 64-bit
* Qt version: Qt 5.9.4
* PyQt version: PyQt5 5.9.2
* Operating System name/version: Windows 10 

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
pygments >=2.0    :  2.2.0 (OK)
qdarkstyle >=2.6.4:  2.6.4 (OK)
sphinx >=0.6.6    :  1.8.1 (OK)
pyls >=0.19.0     :  0.19.0 (OK)
nbconvert >=4.0   :  5.3.1 (OK)
pandas >=0.13.1   :  0.23.4 (OK)
numpy >=1.7       :  1.15.4 (OK)
sympy >=0.7.3     :  None (NOK)
cython >=0.21     :  None (NOK)
qtconsole >=4.2.0 :  4.5.0.dev0 (OK)
IPython >=4.0     :  7.0.1 (OK)
matplotlib >=2.0.0:  3.0.1 (OK)
pylint >=0.25     :  2.1.1 (OK)

```



Discussion:

Resolving patch:
diff --git a/spyder/plugins/projects/plugin.py b/spyder/plugins/projects/plugin.py
--- a/spyder/plugins/projects/plugin.py
+++ b/spyder/plugins/projects/plugin.py
@@ -13,6 +13,7 @@
 
 # Standard library imports
 import os.path as osp
+import shutil
 
 # Third party imports
 from qtpy.compat import getexistingdirectory
@@ -89,7 +90,7 @@ def get_plugin_actions(self):
                                     triggered=self.close_project)
         self.delete_project_action = create_action(self,
                                     _("Delete Project"),
-                                    triggered=self._delete_project)
+                                    triggered=self.delete_project)
         self.clear_recent_projects_action =\
             create_action(self, _("Clear this list"),
                           triggered=self.clear_recent_projects)
@@ -120,6 +121,7 @@ def register_plugin(self):
         self.main.add_dockwidget(self)
         self.explorer.sig_open_file.connect(self.main.open_file)
 
+        treewidget.sig_delete_project.connect(self.delete_project)
         treewidget.sig_edit.connect(self.main.editor.load)
         treewidget.sig_removed.connect(self.main.editor.removed)
         treewidget.sig_removed_tree.connect(self.main.editor.removed_tree)
@@ -331,11 +333,33 @@ def close_project(self):
             self.explorer.clear()
             self.restart_consoles()
 
-    def _delete_project(self):
-        """Delete current project."""
+    def delete_project(self):
+        """
+        Delete the current project without deleting the files in the directory.
+        """
         if self.current_active_project:
             self.switch_to_plugin()
-            self.explorer.delete_project()
+            path = self.current_active_project.root_path
+            buttons = QMessageBox.Yes | QMessageBox.No
+            answer = QMessageBox.warning(
+                self,
+                _("Delete"),
+                _("Do you really want to delete <b>{filename}</b>?<br><br>"
+                  "<b>Note:</b> This action will only delete the project. "
+                  "Its files are going to be preserved on disk."
+                  ).format(filename=osp.basename(path)),
+                buttons)
+            if answer == QMessageBox.Yes:
+                try:
+                    self.close_project()
+                    shutil.rmtree(osp.join(path, '.spyproject'))
+                except EnvironmentError as error:
+                    QMessageBox.critical(
+                        self,
+                        _("Project Explorer"),
+                        _("<b>Unable to delete <i>{varpath}</i></b>"
+                          "<br><br>The error message was:<br>{error}"
+                          ).format(varpath=path, error=to_text_string(error)))
 
     def clear_recent_projects(self):
         """Clear the list of recent projects"""
diff --git a/spyder/plugins/projects/widgets/explorer.py b/spyder/plugins/projects/widgets/explorer.py
--- a/spyder/plugins/projects/widgets/explorer.py
+++ b/spyder/plugins/projects/widgets/explorer.py
@@ -230,32 +230,6 @@ def setup_project(self, directory):
         # Setup the directory shown by the tree
         self.set_project_dir(directory)
 
-        # Signal to delete the project
-        self.treewidget.sig_delete_project.connect(self.delete_project)
-
-    def delete_project(self):
-        """Delete current project without deleting the files in the
-        directory."""
-        if self.current_active_project:
-            path = self.current_active_project.root_path
-            buttons = QMessageBox.Yes | QMessageBox.No
-            answer = QMessageBox.warning(self, _("Delete"),
-                                 _("Do you really want "
-                                   "to delete <b>{filename}</b>?<br><br>"
-                                   "<b>Note:</b> This action will only delete "
-                                   "the project. Its files are going to be "
-                                   "preserved on disk."
-                                   ).format(filename=osp.basename(path)),
-                                   buttons)
-            if answer == QMessageBox.Yes:
-                try:
-                    self.close_project()
-                    shutil.rmtree(osp.join(path,'.spyproject'))
-                except EnvironmentError as error:
-                    QMessageBox.critical(self, _("Project Explorer"),
-                                    _("<b>Unable to delete <i>{varpath}</i></b>"
-                                      "<br><br>The error message was:<br>{error}" )
-                                    .format(varpath=path,error=to_text_string(error)))
 
 #==============================================================================
 # Tests


Test patch:
diff --git a/spyder/plugins/projects/tests/test_plugin.py b/spyder/plugins/projects/tests/test_plugin.py
--- a/spyder/plugins/projects/tests/test_plugin.py
+++ b/spyder/plugins/projects/tests/test_plugin.py
@@ -22,7 +22,7 @@
 
 # Local imports
 import spyder.plugins.base
-from spyder.plugins.projects.plugin import Projects
+from spyder.plugins.projects.plugin import Projects, QMessageBox
 from spyder.py3compat import to_text_string
 
 
@@ -113,6 +113,24 @@ def test_open_project(projects, tmpdir, test_directory):
     projects.close_project()
 
 
+@pytest.mark.parametrize("test_directory", [u'測試', u'اختبار', u"test_dir"])
+def test_delete_project(projects, tmpdir, mocker, test_directory):
+    """Test that we can delete a project."""
+    # Create the directory
+    path = to_text_string(tmpdir.mkdir(test_directory))
+
+    # Open project in path
+    projects.open_project(path=path)
+    assert projects.is_valid_project(path)
+    assert osp.exists(osp.join(path, '.spyproject'))
+
+    # Delete project
+    mocker.patch.object(QMessageBox, 'warning', return_value=QMessageBox.Yes)
+    projects.delete_project()
+    assert not projects.is_valid_project(path)
+    assert not osp.exists(osp.join(path, '.spyproject'))
+
+
 @pytest.mark.parametrize('value', [True, False])
 def test_close_project_sets_visible_config(projects, tmpdir, value):
     """Test that when project is closed, the config option