Repository: napari/napari

Issue:
Add a restart option on the file menu
## 🚀 Feature

Add a restart method to the main application and add it to the corresponding menu.

## Motivation

Some configuration options will need a restart so we need to provide a a way to restart

## Pitch

N.A.

## Alternatives

N.A.

## Additional context

This is part of the configuration/settings work.



Discussion:
+1 to this functionality, thanks for making an issue to track!!

Resolving patch:
diff --git a/napari/__main__.py b/napari/__main__.py
--- a/napari/__main__.py
+++ b/napari/__main__.py
@@ -14,10 +14,10 @@
 from textwrap import wrap
 from typing import Any, Dict, List
 
-from . import Viewer, __version__, layers, run, view_path
-from .components.viewer_model import valid_add_kwargs
-from .utils import citation_text, sys_info
-from .utils.settings import SETTINGS
+from napari import Viewer, __version__, layers, run, view_path
+from napari.components.viewer_model import valid_add_kwargs
+from napari.utils import citation_text, sys_info
+from napari.utils.settings import SETTINGS
 
 
 class InfoAction(argparse.Action):
@@ -25,7 +25,7 @@ def __call__(self, *args, **kwargs):
         # prevent unrelated INFO logs when doing "napari --info"
         logging.basicConfig(level=logging.WARNING)
         print(sys_info())
-        from .plugins import discover_dock_widgets, plugin_manager
+        from napari.plugins import discover_dock_widgets, plugin_manager
 
         discover_dock_widgets()
         errors = plugin_manager.get_errors()
@@ -41,7 +41,7 @@ class PluginInfoAction(argparse.Action):
     def __call__(self, *args, **kwargs):
         # prevent unrelated INFO logs when doing "napari --info"
         logging.basicConfig(level=logging.WARNING)
-        from .plugins import discover_dock_widgets, plugin_manager
+        from napari.plugins import discover_dock_widgets, plugin_manager
 
         discover_dock_widgets()
         print(plugin_manager)
@@ -266,7 +266,7 @@ def _run():
 
     else:
         if args.with_:
-            from . import plugins
+            from napari import plugins
 
             # if a plugin widget has been requested, this will fail immediately
             # if the requested plugin/widget is not available.
@@ -278,7 +278,7 @@ def _run():
             else:
                 plugins.get_plugin_widget(pname)
 
-        from ._qt.widgets.qt_splash_screen import NapariSplashScreen
+        from napari._qt.widgets.qt_splash_screen import NapariSplashScreen
 
         splash = NapariSplashScreen()
         splash.close()  # will close once event loop starts
@@ -310,7 +310,7 @@ def _run():
 
 def _run_plugin_module(mod, plugin_name):
     """Register `mod` as a plugin, find/create viewer, and run napari."""
-    from .plugins import plugin_manager
+    from napari.plugins import plugin_manager
 
     plugin_manager.register(mod, name=plugin_name)
 
diff --git a/napari/_qt/qt_main_window.py b/napari/_qt/qt_main_window.py
--- a/napari/_qt/qt_main_window.py
+++ b/napari/_qt/qt_main_window.py
@@ -4,11 +4,12 @@
 """
 import inspect
 import os
+import sys
 import time
 from itertools import chain, repeat
 from typing import Dict
 
-from qtpy.QtCore import QPoint, QSize, Qt
+from qtpy.QtCore import QPoint, QProcess, QSize, Qt
 from qtpy.QtGui import QIcon, QKeySequence
 from qtpy.QtWidgets import (
     QAction,
@@ -26,7 +27,7 @@
 from .. import plugins
 from ..utils import config, perf
 from ..utils.io import imsave
-from ..utils.misc import in_jupyter
+from ..utils.misc import in_jupyter, running_as_bundled_app
 from ..utils.settings import SETTINGS
 from ..utils.translations import trans
 from .dialogs.preferences_dialog import PreferencesDialog
@@ -237,6 +238,17 @@ def closeEvent(self, event):
 
         event.accept()
 
+    def restart(self):
+        """Restart the napari application in a detached process."""
+        process = QProcess()
+        process.setProgram(sys.executable)
+
+        if not running_as_bundled_app():
+            process.setArguments(sys.argv)
+
+        process.startDetached()
+        self.close(quit_app=True)
+
 
 class Window:
     """Application window that contains the menu bar and viewer.
@@ -445,6 +457,10 @@ def _add_file_menu(self):
             lambda: self._qt_window.close(quit_app=True)
         )
 
+        if running_as_bundled_app():
+            restartAction = QAction(trans._('Restart'), self._qt_window)
+            restartAction.triggered.connect(self._qt_window.restart)
+
         closeAction = QAction(trans._('Close Window'), self._qt_window)
         closeAction.setShortcut('Ctrl+W')
         closeAction.triggered.connect(self._qt_window.close_window)
@@ -490,6 +506,10 @@ def _add_sample(*args, plg=plugin_name, smp=samp_name):
         self.file_menu.addAction(screenshot_wv)
         self.file_menu.addSeparator()
         self.file_menu.addAction(closeAction)
+
+        if running_as_bundled_app():
+            self.file_menu.addAction(restartAction)
+
         self.file_menu.addAction(quitAction)
 
     def _open_preferences(self):
@@ -1188,6 +1208,10 @@ def _screenshot_dialog(self):
         if dial.exec_():
             self._last_visited_dir = os.path.dirname(dial.selectedFiles()[0])
 
+    def _restart(self):
+        """Restart the napari application."""
+        self._qt_window.restart()
+
     def screenshot(self, path=None):
         """Take currently displayed viewer and convert to an image array.
 


Test patch: