Repository: spyder-ide/spyder
Pull request: PR: Save last accepted kernel settings in config
<!--- Before submitting your pull request, --->
<!--- please complete as much as practicable of the following checklist: --->

### Pull Request Checklist

* [x] Read and followed this repo's [Contributing Guidelines](https://github.com/spyder-ide/spyder/blob/master/CONTRIBUTING.md)
* [x] Based this PR on the latest version of the correct branch (master or 3.x)
* [x] Followed [PEP 8](https://www.python.org/dev/peps/pep-0008/) code style
* [x] Ensured this pull request hasn't eliminated unrelated blank lines/spaces,
      modified the ``spyder/defaults`` directory, or added new icons/assets
* [x] Wrote at least one-line docstrings following PEP 257for any new functions
* [x] Added at least one unit test covering the changes, if at all possible
* [x] Described the changes and the motivation for them below
* [x] Noted what issue(s) this pull request resolves, creating one if needed
* [x] Included a screenshot, if this PR makes any visible changes to the UI


### Developer Certificate of Origin Affirmation

By submitting this Pull Request or typing my name below, I affirm the
[Developer Certificate of Origin](https://developercertificate.org/)
with respect to both the content of the contribution itself and this post,
and understand I am releasing it under Spyder's MIT (Expat) license.


I certify the above statement is true and correct:
afzalive
<!--- Note that you (not Spyder) retain copyright ownership of your work, --->
<!--- and may license it to other parties under the terms of your choice. --->
<!--- Contact us before incorporating any content from external projects. --->


## Description of Changes

I use remote kernels quite often and every time I start a new kernel on a remote machine, I have to fill in all the fields. So finally, this change allows saving these fields so all you have to do is press enter.

In a new section called "existing-kernel" in the CONF, I'm saving the following fields when the user has checked "Save connection settings" checkbox and clicks "Ok" in the existing kernel dialog:

- json_file_path (default: blank)
- is_remote (default: unchecked)
- username (default: blank)
- hostname (default: blank)
- is_ssh_keyfile (default: password radio selected, so false)
- port (default: 22)
- ssh_key_file_path (default: blank)

And then populating these fields when the dialog is created (__init__). If these fields don't already exist in the CONF, default values (as mentioned above are used).

The following fields are saved in the keyring, under "spyder_remote_kernel":

- ssh password
- ssh key passphrase

There are 4 test cases to test this feature:

- Save user input and ssh key passphrase when save checkbox is checked
- Do not save user input and ssh key passphrase when save checkbox is not checked
- Save user input and ssh key password when save checkbox is checked
- Do not Save user input and ssh key password when save checkbox is not checked


### Screenshot

![image](https://user-images.githubusercontent.com/436057/49190176-415e7a00-f33f-11e8-9d9c-d929faee3af7.png)


### Issue(s) Resolved

Fixes #3689

Thank you

<!--- Thanks for your help making Spyder better for everyone! --->

Problem statement:
How to remember the configurations of connecting to remote kernel?
Hello,
       I can update that remote kernel josn manually,but how to remember the configurations(remote kernel json file location,host,ssh key file location,password) of connectiong to remote kernel?Then I don't need to configure it manually every time.

Hints from issue discussion:
There's no option to do that right now, but it's an interesting suggestion. We'll work on it for a future Spyder version :-)

Second that; it's a hassle typing everything again each time since I am connecting through SSH. Maybe add a "save password" option, too.

Commit message: Connected save_connection_settings to accept button

Added test cases for remembering password, and not remembering settings if checkbox is unchecked
Added proper teardown
Click ok button in test cases instead of calling save_connection_settings
Added docstrings
Added linux/CI check in test

Patch to review:
diff --git a/spyder/plugins/ipythonconsole/widgets/kernelconnect.py b/spyder/plugins/ipythonconsole/widgets/kernelconnect.py
index c035925e0cc..0af82e952a8 100644
--- a/spyder/plugins/ipythonconsole/widgets/kernelconnect.py
+++ b/spyder/plugins/ipythonconsole/widgets/kernelconnect.py
@@ -15,12 +15,14 @@
 from jupyter_core.paths import jupyter_runtime_dir
 from qtpy.compat import getopenfilename
 from qtpy.QtCore import Qt
-from qtpy.QtWidgets import (QDialog, QDialogButtonBox, QGridLayout, QGroupBox,
-                            QHBoxLayout, QLabel, QLineEdit, QPushButton,
-                            QRadioButton, QSpacerItem, QVBoxLayout)
+from qtpy.QtWidgets import (QCheckBox, QDialog, QDialogButtonBox, QGridLayout,
+                            QGroupBox, QHBoxLayout, QLabel, QLineEdit,
+                            QPushButton, QRadioButton, QSpacerItem,
+                            QVBoxLayout)
 
 # Local imports
 from spyder.config.base import _, get_home_dir
+from spyder.config.main import CONF
 
 
 class KernelConnectionDialog(QDialog):
@@ -64,7 +66,7 @@ def __init__(self, parent=None):
         pn_label = QLabel(_('Port:'))
         self.pn = QLineEdit()
         self.pn.setMaximumWidth(75)
-        self.pn.setText('22')
+
         un_label = QLabel(_('Username:'))
         self.un = QLineEdit()
 
@@ -129,7 +131,6 @@ def __init__(self, parent=None):
         rm_layout.addWidget(auth_group)
         self.rm_group.setLayout(rm_layout)
         self.rm_group.setCheckable(True)
-        self.rm_group.setChecked(False)
         self.rm_group.toggled.connect(self.pw_radio.setChecked)
 
         # Ok and Cancel buttons
@@ -137,9 +138,18 @@ def __init__(self, parent=None):
             QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
             Qt.Horizontal, self)
 
+        self.accept_btns.accepted.connect(self.save_connection_settings)
         self.accept_btns.accepted.connect(self.accept)
         self.accept_btns.rejected.connect(self.reject)
 
+        # Save connection settings checkbox
+        self.save_layout = QCheckBox(self)
+        self.save_layout.setText(_("Save connection settings"))
+
+        btns_layout = QHBoxLayout()
+        btns_layout.addWidget(self.save_layout)
+        btns_layout.addWidget(self.accept_btns)
+
         # Dialog layout
         layout = QVBoxLayout(self)
         layout.addWidget(main_label)
@@ -147,7 +157,78 @@ def __init__(self, parent=None):
         layout.addLayout(cf_layout)
         layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12)))
         layout.addWidget(self.rm_group)
-        layout.addWidget(self.accept_btns)
+        layout.addLayout(btns_layout)
+
+        self.load_connection_settings()
+
+    """Loads the user's previously-saved remote kernel connection settings."""
+    def load_connection_settings(self):
+        existing_kernel = CONF.get("existing-kernel", "settings", {})
+
+        cfp = existing_kernel.get("json_file_path", "")
+        is_remote = existing_kernel.get("is_remote", False)
+        username = existing_kernel.get("username", "")
+        hostname = existing_kernel.get("hostname", "")
+        port = str(existing_kernel.get("port", 22))
+        is_ssh_kf = existing_kernel.get("is_ssh_keyfile", False)
+        ssh_kf = existing_kernel.get("ssh_key_file_path", "")
+
+        if cfp != "":
+            self.cf.setText(cfp)
+        if username != "":
+            self.un.setText(username)
+        if hostname != "":
+            self.hn.setText(hostname)
+        if ssh_kf != "":
+            self.kf.setText(ssh_kf)
+        self.rm_group.setChecked(is_remote)
+        self.pn.setText(port)
+        self.kf_radio.setChecked(is_ssh_kf)
+        self.pw_radio.setChecked(not is_ssh_kf)
+
+        try:
+            import keyring
+            ssh_passphrase = keyring.get_password("existing_kernel",
+                                                  "ssh_key_passphrase")
+            ssh_password = keyring.get_password("existing_kernel",
+                                                "ssh_password")
+            if ssh_passphrase != "":
+                self.kfp.setText(ssh_passphrase)
+            if ssh_password != "":
+                self.pw.setText(ssh_password)
+        except Exception:
+            pass
+
+    def save_connection_settings(self):
+        """Saves user's remote kernel connection settings."""
+
+        if not self.save_layout.isChecked():
+            return
+
+        is_ssh_key = bool(self.kf_radio.isChecked())
+        connection_settings = {
+            "json_file_path": self.cf.text(),
+            "is_remote": self.rm_group.isChecked(),
+            "username": self.un.text(),
+            "hostname": self.hn.text(),
+            "port": self.pn.text(),
+            "is_ssh_keyfile": is_ssh_key,
+            "ssh_key_file_path": self.kf.text()
+        }
+        CONF.set("existing-kernel", "settings", connection_settings)
+
+        try:
+            import keyring
+            if is_ssh_key:
+                keyring.set_password("existing_kernel",
+                                     "ssh_key_passphrase",
+                                     self.kfp.text())
+            else:
+                keyring.set_password("existing_kernel",
+                                     "ssh_password",
+                                     self.pw.text())
+        except Exception:
+            pass
 
     def select_connection_file(self):
         cf = getopenfilename(self, _('Select kernel connection file'),
@@ -166,6 +247,7 @@ def get_connection_parameters(parent=None, dialog=None):
         result = dialog.exec_()
         is_remote = bool(dialog.rm_group.isChecked())
         accepted = result == QDialog.Accepted
+
         if is_remote:
             def falsy_to_none(arg):
                 return arg if arg else None
diff --git a/spyder/plugins/ipythonconsole/widgets/tests/test_kernelconnect.py b/spyder/plugins/ipythonconsole/widgets/tests/test_kernelconnect.py
new file mode 100644
index 00000000000..635ba307599
--- /dev/null
+++ b/spyder/plugins/ipythonconsole/widgets/tests/test_kernelconnect.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright © Spyder Project Contributors
+#
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+# -----------------------------------------------------------------------------
+
+"""Tests for the Existing Kernel Connection widget."""
+
+# Third party imports
+import sys
+import os
+import pytest
+from qtpy.QtCore import Qt
+from qtpy.QtWidgets import QDialogButtonBox
+import time
+
+# Local imports
+from spyder.plugins.ipythonconsole.widgets.kernelconnect import (
+    KernelConnectionDialog
+)
+from spyder.config.main import CONF
+
+
+# =============================================================================
+# Fixtures
+# =============================================================================
+@pytest.fixture
+def connection_dialog_factory(request):
+    """Set up kernel connection dialog."""
+    class DialogFactory(object):
+        def get(self):
+            dialog = KernelConnectionDialog()
+            request.addfinalizer(dialog.close)
+            return dialog
+
+    def teardown():
+        """Clear existing-kernel config and keyring passwords"""
+        CONF.remove_section("existing-kernel")
+
+        try:
+            import keyring
+            keyring.set_password("existing_kernel",
+                                 "ssh_key_passphrase", "")
+            keyring.set_password("existing_kernel",
+                                 "ssh_password", "")
+        except Exception:
+            pass
+
+    request.addfinalizer(teardown)
+    return DialogFactory()
+
+
+# =============================================================================
+# Tests
+# =============================================================================
+def test_kernel_connection_dialog_remembers_input_ssh_passphrase(
+    qtbot, connection_dialog_factory
+):
+    """Test to ensure that the dialog remembers user's kernel connection
+       settings and ssh key passphrase when the user checks the
+       save checkbox"""
+
+    dlg = connection_dialog_factory.get()
+
+    cf_path = "test_cf_path"
+    un = "test_username"
+    hn = "test_hostname"
+    pn = 123
+    kf = "test_kf"
+    kfp = "test_kfp"
+
+    # fill out cf path
+    dlg.cf.clear()
+    qtbot.keyClicks(dlg.cf, cf_path)
+    # check the remote kernel box
+    dlg.rm_group.setChecked(True)
+    # fill out hostname
+    dlg.hn.clear()
+    qtbot.keyClicks(dlg.hn, hn)
+    # fill out port
+    dlg.pn.clear()
+    qtbot.keyClicks(dlg.pn, str(pn))
+    # fill out username
+    dlg.un.clear()
+    qtbot.keyClicks(dlg.un, un)
+
+    # select ssh keyfile radio
+    dlg.kf_radio.setChecked(True)
+    assert dlg.kf.isEnabled()
+
+    # fill out ssh keyfile
+    dlg.kf.clear()
+    qtbot.keyClicks(dlg.kf, kf)
+
+    # fill out passphrase
+    dlg.kfp.clear()
+    qtbot.keyClicks(dlg.kfp, kfp)
+
+    # check save connection settings
+    dlg.save_layout.setChecked(True)
+
+    # Press ok and save connection settings
+    qtbot.mouseClick(dlg.accept_btns.button(QDialogButtonBox.Ok),
+                     Qt.LeftButton)
+
+    # create new dialog and check fields
+    new_dlg = connection_dialog_factory.get()
+    assert new_dlg.cf.text() == cf_path
+    assert new_dlg.rm_group.isChecked()
+    assert new_dlg.hn.text() == hn
+    assert new_dlg.un.text() == un
+    assert new_dlg.pn.text() == str(pn)
+    assert new_dlg.kf.text() == kf
+    if (not sys.platform.startswith('linux') or
+            not os.environ.get('CI') is not None):
+        assert new_dlg.kfp.text() == kfp
+
+
+def test_kernel_connection_dialog_doesnt_remembers_input_ssh_passphrase(
+    qtbot, connection_dialog_factory
+):
+    """Test to ensure that the dialog doesn't remembers user's kernel
+       connection settings and ssh key passphrase when the user doesn't
+       check the save checkbox"""
+
+    dlg = connection_dialog_factory.get()
+
+    cf_path = "test_cf_path"
+    un = "test_username"
+    hn = "test_hostname"
+    pn = 123
+    kf = "test_kf"
+    kfp = "test_kfp"
+
+    # fill out cf path
+    dlg.cf.clear()
+    qtbot.keyClicks(dlg.cf, cf_path)
+    # check the remote kernel box
+    dlg.rm_group.setChecked(True)
+    # fill out hostname
+    dlg.hn.clear()
+    qtbot.keyClicks(dlg.hn, hn)
+    # fill out port
+    dlg.pn.clear()
+    qtbot.keyClicks(dlg.pn, str(pn))
+    # fill out username
+    dlg.un.clear()
+    qtbot.keyClicks(dlg.un, un)
+
+    # select ssh keyfile radio
+    dlg.kf_radio.setChecked(True)
+    assert dlg.kf.isEnabled()
+
+    # fill out ssh keyfile
+    dlg.kf.clear()
+    qtbot.keyClicks(dlg.kf, kf)
+
+    # fill out passphrase
+    dlg.kfp.clear()
+    qtbot.keyClicks(dlg.kfp, kfp)
+
+    # check save connection settings
+    dlg.save_layout.setChecked(False)
+
+    # Press ok and save connection settings
+    qtbot.mouseClick(dlg.accept_btns.button(QDialogButtonBox.Ok),
+                     Qt.LeftButton)
+
+    # create new dialog and check fields
+    new_dlg = connection_dialog_factory.get()
+    assert new_dlg.cf.text() == ""
+    assert not new_dlg.rm_group.isChecked()
+    assert new_dlg.hn.text() == ""
+    assert new_dlg.un.text() == ""
+    assert new_dlg.pn.text() == "22"
+    assert new_dlg.kf.text() == ""
+    if (not sys.platform.startswith('linux') or
+            not os.environ.get('CI') is not None):
+        assert new_dlg.kfp.text() == ""
+
+
+def test_kernel_connection_dialog_remembers_input_password(
+    qtbot, connection_dialog_factory
+):
+    """Test to ensure that the dialog remembers user's kernel connection
+       settings and ssh password when the user checks the save checkbox"""
+
+    dlg = connection_dialog_factory.get()
+
+    cf_path = "test_cf_path"
+    un = "test_username"
+    hn = "test_hostname"
+    pn = 123
+    pw = "test_pw"
+
+    # fill out cf path
+    # dlg.cf.clear()
+    qtbot.keyClicks(dlg.cf, cf_path)
+    # check the remote kernel box
+    dlg.rm_group.setChecked(True)
+    # fill out hostname
+    # dlg.hn.clear()
+    qtbot.keyClicks(dlg.hn, hn)
+    # fill out port
+    dlg.pn.clear()
+    qtbot.keyClicks(dlg.pn, str(pn))
+    # fill out username
+    dlg.un.clear()
+    qtbot.keyClicks(dlg.un, un)
+
+    # select password radio
+    dlg.pw_radio.setChecked(True)
+    assert dlg.pw.isEnabled()
+
+    # fill out password
+    dlg.pw.clear()
+    qtbot.keyClicks(dlg.pw, pw)
+
+    # check save connection settings
+    dlg.save_layout.setChecked(True)
+
+    # Press ok and save connection settings
+    qtbot.mouseClick(dlg.accept_btns.button(QDialogButtonBox.Ok),
+                     Qt.LeftButton)
+
+    # create new dialog and check fields
+    new_dlg = connection_dialog_factory.get()
+    assert new_dlg.cf.text() == cf_path
+    assert new_dlg.rm_group.isChecked()
+    assert new_dlg.hn.text() == hn
+    assert new_dlg.un.text() == un
+    assert new_dlg.pn.text() == str(pn)
+    if (not sys.platform.startswith('linux') or
+            not os.environ.get('CI') is not None):
+        assert new_dlg.pw.text() == pw
+
+
+def test_kernel_connection_dialog_doesnt_remember_input_password(
+    qtbot, connection_dialog_factory
+):
+    """Test to ensure that the dialog doesn't remembers user's kernel
+       connection settings and ssh password when the user doesn't
+       check the save checkbox"""
+
+    dlg = connection_dialog_factory.get()
+
+    cf_path = "test_cf_path"
+    un = "test_username"
+    hn = "test_hostname"
+    pn = 123
+    pw = "testpw"
+
+    # fill out cf path
+    dlg.cf.clear()
+    qtbot.keyClicks(dlg.cf, cf_path)
+    # check the remote kernel box
+    dlg.rm_group.setChecked(True)
+    # fill out hostname
+    dlg.hn.clear()
+    qtbot.keyClicks(dlg.hn, hn)
+    # fill out port
+    dlg.pn.clear()
+    qtbot.keyClicks(dlg.pn, str(pn))
+    # fill out username
+    dlg.un.clear()
+    qtbot.keyClicks(dlg.un, un)
+
+    # select password radio
+    dlg.pw_radio.setChecked(True)
+    assert dlg.pw.isEnabled()
+
+    # fill out password
+    dlg.pw.clear()
+    qtbot.keyClicks(dlg.pw, pw)
+
+    # check save connection settings
+    dlg.save_layout.setChecked(False)
+
+    # Press ok and save connection settings
+    qtbot.mouseClick(dlg.accept_btns.button(QDialogButtonBox.Ok),
+                     Qt.LeftButton)
+
+    # create new dialog and check fields
+    new_dlg = connection_dialog_factory.get()
+    assert new_dlg.cf.text() == ""
+    assert not new_dlg.rm_group.isChecked()
+    assert new_dlg.hn.text() == ""
+    assert new_dlg.un.text() == ""
+    assert new_dlg.pn.text() == "22"
+    if (not sys.platform.startswith('linux') or
+            not os.environ.get('CI') is not None):
+        assert new_dlg.pw.text() == ""
+
+
+if __name__ == "__main__":
+    pytest.main()


Code review comments:
diff_hunk: @@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright © Spyder Project Contributors
+#
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+# -----------------------------------------------------------------------------
+
+"""Tests for the Existing Kernel Connection widget."""
+
+# Third party imports
+import sys
+import os
+import pytest
+from qtpy.QtCore import Qt
+from qtpy.QtWidgets import QDialogButtonBox
+import time
+
+# Local imports
+from spyder.plugins.ipythonconsole.widgets.kernelconnect import (
+    KernelConnectionDialog
+)
+from spyder.config.main import CONF
+
+
+# =============================================================================
+# Fixtures
+# =============================================================================
+@pytest.fixture
+def connection_dialog_factory(request):
+    """Set up kernel connection dialog."""
+    class DialogFactory(object):
+        def get(self):
+            dialog = KernelConnectionDialog()
+            request.addfinalizer(dialog.close)
+            return dialog
+
+    def teardown():
+        """Clear existing-kernel config and keyring passwords"""
+        CONF.remove_section("existing-kernel")
+
+        try:
+            import keyring
+            keyring.set_password("existing_kernel",
+                                 "ssh_key_passphrase", "")
+            keyring.set_password("existing_kernel",
+                                 "ssh_password", "")
+        except Exception:
+            pass
+
+    request.addfinalizer(teardown)
+    return DialogFactory()
+
+
+# =============================================================================
+# Tests
+# =============================================================================
+def test_kernel_connection_dialog_remembers_input_ssh_passphrase(
+    qtbot, connection_dialog_factory
path: spyder/plugins/ipythonconsole/widgets/tests/test_kernelconnect.py
text: @user1:
Per PEP8 and our convention, hanging indent continuations of ``def`` statements should be indented two levels, i.e. 8 spaces, and the closing paren should be on the same line as the last parameter. Also, I appreciatiate the descriptiveness but this is probably a little much. Trimming some redundant words:

``test_connection_dialog_remembers_passphrase``

diff_hunk: @@ -129,25 +131,104 @@ def __init__(self, parent=None):
         rm_layout.addWidget(auth_group)
         self.rm_group.setLayout(rm_layout)
         self.rm_group.setCheckable(True)
-        self.rm_group.setChecked(False)
         self.rm_group.toggled.connect(self.pw_radio.setChecked)
 
         # Ok and Cancel buttons
         self.accept_btns = QDialogButtonBox(
             QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
             Qt.Horizontal, self)
 
+        self.accept_btns.accepted.connect(self.save_connection_settings)
         self.accept_btns.accepted.connect(self.accept)
         self.accept_btns.rejected.connect(self.reject)
 
+        # Save connection settings checkbox
+        self.save_layout = QCheckBox(self)
+        self.save_layout.setText(_("Save connection settings"))
+
+        btns_layout = QHBoxLayout()
+        btns_layout.addWidget(self.save_layout)
+        btns_layout.addWidget(self.accept_btns)
+
         # Dialog layout
         layout = QVBoxLayout(self)
         layout.addWidget(main_label)
         layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
         layout.addLayout(cf_layout)
         layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12)))
         layout.addWidget(self.rm_group)
-        layout.addWidget(self.accept_btns)
+        layout.addLayout(btns_layout)
+
+        self.load_connection_settings()
+
+    """Loads the user's previously-saved remote kernel connection settings."""
+    def load_connection_settings(self):
+        existing_kernel = CONF.get("existing-kernel", "settings", {})
+
+        cfp = existing_kernel.get("json_file_path", "")
+        is_remote = existing_kernel.get("is_remote", False)
+        username = existing_kernel.get("username", "")
+        hostname = existing_kernel.get("hostname", "")
+        port = str(existing_kernel.get("port", 22))
+        is_ssh_kf = existing_kernel.get("is_ssh_keyfile", False)
+        ssh_kf = existing_kernel.get("ssh_key_file_path", "")
+
+        if cfp != "":
+            self.cf.setText(cfp)
+        if username != "":
+            self.un.setText(username)
+        if hostname != "":
+            self.hn.setText(hostname)
+        if ssh_kf != "":
+            self.kf.setText(ssh_kf)
+        self.rm_group.setChecked(is_remote)
+        self.pn.setText(port)
+        self.kf_radio.setChecked(is_ssh_kf)
+        self.pw_radio.setChecked(not is_ssh_kf)
+
+        try:
+            import keyring
+            ssh_passphrase = keyring.get_password("existing_kernel",
+                                                  "ssh_key_passphrase")
+            ssh_password = keyring.get_password("existing_kernel",
+                                                "ssh_password")
+            if ssh_passphrase != "":
+                self.kfp.setText(ssh_passphrase)
+            if ssh_password != "":
path: spyder/plugins/ipythonconsole/widgets/kernelconnect.py
text: @user1:
``if ssh_password``?

diff_hunk: @@ -129,25 +131,104 @@ def __init__(self, parent=None):
         rm_layout.addWidget(auth_group)
         self.rm_group.setLayout(rm_layout)
         self.rm_group.setCheckable(True)
-        self.rm_group.setChecked(False)
         self.rm_group.toggled.connect(self.pw_radio.setChecked)
 
         # Ok and Cancel buttons
         self.accept_btns = QDialogButtonBox(
             QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
             Qt.Horizontal, self)
 
+        self.accept_btns.accepted.connect(self.save_connection_settings)
         self.accept_btns.accepted.connect(self.accept)
         self.accept_btns.rejected.connect(self.reject)
 
+        # Save connection settings checkbox
+        self.save_layout = QCheckBox(self)
+        self.save_layout.setText(_("Save connection settings"))
+
+        btns_layout = QHBoxLayout()
+        btns_layout.addWidget(self.save_layout)
+        btns_layout.addWidget(self.accept_btns)
+
         # Dialog layout
         layout = QVBoxLayout(self)
         layout.addWidget(main_label)
         layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
         layout.addLayout(cf_layout)
         layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12)))
         layout.addWidget(self.rm_group)
-        layout.addWidget(self.accept_btns)
+        layout.addLayout(btns_layout)
+
+        self.load_connection_settings()
+
+    """Loads the user's previously-saved remote kernel connection settings."""
path: spyder/plugins/ipythonconsole/widgets/kernelconnect.py
text: @user1:
This needs to go inside the actual function (i.e. *below* the ``def`` line) and should be in imperative tense ("Load the user's...) like all docstrings, commit messages and comments. Sorry for my silly typo originally.

diff_hunk: @@ -129,25 +131,104 @@ def __init__(self, parent=None):
         rm_layout.addWidget(auth_group)
         self.rm_group.setLayout(rm_layout)
         self.rm_group.setCheckable(True)
-        self.rm_group.setChecked(False)
         self.rm_group.toggled.connect(self.pw_radio.setChecked)
 
         # Ok and Cancel buttons
         self.accept_btns = QDialogButtonBox(
             QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
             Qt.Horizontal, self)
 
+        self.accept_btns.accepted.connect(self.save_connection_settings)
         self.accept_btns.accepted.connect(self.accept)
         self.accept_btns.rejected.connect(self.reject)
 
+        # Save connection settings checkbox
+        self.save_layout = QCheckBox(self)
+        self.save_layout.setText(_("Save connection settings"))
+
+        btns_layout = QHBoxLayout()
+        btns_layout.addWidget(self.save_layout)
+        btns_layout.addWidget(self.accept_btns)
+
         # Dialog layout
         layout = QVBoxLayout(self)
         layout.addWidget(main_label)
         layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
         layout.addLayout(cf_layout)
         layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12)))
         layout.addWidget(self.rm_group)
-        layout.addWidget(self.accept_btns)
+        layout.addLayout(btns_layout)
+
+        self.load_connection_settings()
+
+    """Loads the user's previously-saved remote kernel connection settings."""
+    def load_connection_settings(self):
+        existing_kernel = CONF.get("existing-kernel", "settings", {})
+
+        cfp = existing_kernel.get("json_file_path", "")
path: spyder/plugins/ipythonconsole/widgets/kernelconnect.py
text: @user1:
Can you rename this variable to be more descriptive? I have no idea what ``cfp`` is supposed to stand for.

diff_hunk: @@ -129,25 +131,104 @@ def __init__(self, parent=None):
         rm_layout.addWidget(auth_group)
         self.rm_group.setLayout(rm_layout)
         self.rm_group.setCheckable(True)
-        self.rm_group.setChecked(False)
         self.rm_group.toggled.connect(self.pw_radio.setChecked)
 
         # Ok and Cancel buttons
         self.accept_btns = QDialogButtonBox(
             QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
             Qt.Horizontal, self)
 
+        self.accept_btns.accepted.connect(self.save_connection_settings)
         self.accept_btns.accepted.connect(self.accept)
         self.accept_btns.rejected.connect(self.reject)
 
+        # Save connection settings checkbox
+        self.save_layout = QCheckBox(self)
+        self.save_layout.setText(_("Save connection settings"))
+
+        btns_layout = QHBoxLayout()
+        btns_layout.addWidget(self.save_layout)
+        btns_layout.addWidget(self.accept_btns)
+
         # Dialog layout
         layout = QVBoxLayout(self)
         layout.addWidget(main_label)
         layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
         layout.addLayout(cf_layout)
         layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12)))
         layout.addWidget(self.rm_group)
-        layout.addWidget(self.accept_btns)
+        layout.addLayout(btns_layout)
+
+        self.load_connection_settings()
+
+    """Loads the user's previously-saved remote kernel connection settings."""
+    def load_connection_settings(self):
+        existing_kernel = CONF.get("existing-kernel", "settings", {})
+
+        cfp = existing_kernel.get("json_file_path", "")
+        is_remote = existing_kernel.get("is_remote", False)
+        username = existing_kernel.get("username", "")
+        hostname = existing_kernel.get("hostname", "")
+        port = str(existing_kernel.get("port", 22))
+        is_ssh_kf = existing_kernel.get("is_ssh_keyfile", False)
+        ssh_kf = existing_kernel.get("ssh_key_file_path", "")
+
+        if cfp != "":
+            self.cf.setText(cfp)
+        if username != "":
+            self.un.setText(username)
+        if hostname != "":
+            self.hn.setText(hostname)
+        if ssh_kf != "":
+            self.kf.setText(ssh_kf)
+        self.rm_group.setChecked(is_remote)
+        self.pn.setText(port)
+        self.kf_radio.setChecked(is_ssh_kf)
+        self.pw_radio.setChecked(not is_ssh_kf)
+
+        try:
+            import keyring
+            ssh_passphrase = keyring.get_password("existing_kernel",
+                                                  "ssh_key_passphrase")
+            ssh_password = keyring.get_password("existing_kernel",
+                                                "ssh_password")
+            if ssh_passphrase != "":
path: spyder/plugins/ipythonconsole/widgets/kernelconnect.py
text: @user1:
``if ssh_passphrase``?