# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""
Language Server Protocol advanced configuration tab.
"""
# Standard library imports
import re
# Third party imports
from qtpy.QtCore import Qt, Slot
from qtpy.QtWidgets import (QGroupBox, QGridLayout, QLabel, QMessageBox,
QVBoxLayout, QWidget)
# Local imports
from spyder.api.preferences import SpyderPreferencesTab
from spyder.config.base import _
from spyder.utils.misc import check_connection_port
class AdvancedConfigTab(SpyderPreferencesTab):
"""PyLS advanced configuration tab."""
TITLE = _('Advanced')
def __init__(self, parent):
super().__init__(parent)
lsp_advanced_group = QGroupBox(_(
'Python Language Server configuration'))
advanced_label = QLabel(
_("Warning: Only modify these values if "
"you know what you're doing!"))
advanced_label.setWordWrap(True)
advanced_label.setAlignment(Qt.AlignJustify)
# Advanced settings checkbox
self.advanced_options_check = self.create_checkbox(
_("Enable advanced settings"), 'advanced/enabled')
# Advanced options
self.advanced_module = self.create_lineedit(
_("Module for the Python language server: "),
'advanced/module', alignment=Qt.Horizontal,
word_wrap=False)
self.advanced_host = self.create_lineedit(
_("IP Address and port to bind the server to: "),
'advanced/host', alignment=Qt.Horizontal,
word_wrap=False)
self.advanced_port = self.create_spinbox(
":", "", 'advanced/port', min_=1, max_=65535, step=1)
self.external_server = self.create_checkbox(
_("This is an external server"),
'advanced/external')
self.use_stdio = self.create_checkbox(
_("Use stdio pipes to communicate with server"),
'advanced/stdio')
self.use_stdio.stateChanged.connect(self.disable_tcp)
self.external_server.stateChanged.connect(self.disable_stdio)
# Advanced layout
advanced_g_layout = QGridLayout()
advanced_g_layout.addWidget(self.advanced_module.label, 1, 0)
advanced_g_layout.addWidget(self.advanced_module.textbox, 1, 1)
advanced_g_layout.addWidget(self.advanced_host.label, 2, 0)
advanced_host_port_g_layout = QGridLayout()
advanced_host_port_g_layout.addWidget(self.advanced_host.textbox, 1, 0)
advanced_host_port_g_layout.addWidget(self.advanced_port.plabel, 1, 1)
advanced_host_port_g_layout.addWidget(self.advanced_port.spinbox, 1, 2)
advanced_g_layout.addLayout(advanced_host_port_g_layout, 2, 1)
# External server and stdio options layout
advanced_server_layout = QVBoxLayout()
advanced_server_layout.addWidget(self.external_server)
advanced_server_layout.addWidget(self.use_stdio)
advanced_options_layout = QVBoxLayout()
advanced_options_layout.addLayout(advanced_g_layout)
advanced_options_layout.addLayout(advanced_server_layout)
# Set advanced options enabled/disabled
advanced_options_widget = QWidget()
advanced_options_widget.setLayout(advanced_options_layout)
advanced_options_widget.setEnabled(self.get_option('advanced/enabled'))
self.advanced_options_check.toggled.connect(
advanced_options_widget.setEnabled)
self.advanced_options_check.toggled.connect(
self.show_advanced_warning)
# Advanced options layout
advanced_layout = QVBoxLayout()
advanced_layout.addWidget(advanced_label)
advanced_layout.addWidget(self.advanced_options_check)
advanced_layout.addWidget(advanced_options_widget)
lsp_advanced_group.setLayout(advanced_layout)
layout = QVBoxLayout()
layout.addWidget(lsp_advanced_group)
self.setLayout(layout)
def disable_tcp(self, state):
if state == Qt.Checked:
self.advanced_host.textbox.setEnabled(False)
self.advanced_port.spinbox.setEnabled(False)
self.external_server.stateChanged.disconnect()
self.external_server.setChecked(False)
self.external_server.setEnabled(False)
else:
self.advanced_host.textbox.setEnabled(True)
self.advanced_port.spinbox.setEnabled(True)
self.external_server.setChecked(False)
self.external_server.setEnabled(True)
self.external_server.stateChanged.connect(self.disable_stdio)
def disable_stdio(self, state):
if state == Qt.Checked:
self.advanced_host.textbox.setEnabled(True)
self.advanced_port.spinbox.setEnabled(True)
self.advanced_module.textbox.setEnabled(False)
self.use_stdio.stateChanged.disconnect()
self.use_stdio.setChecked(False)
self.use_stdio.setEnabled(False)
else:
self.advanced_host.textbox.setEnabled(True)
self.advanced_port.spinbox.setEnabled(True)
self.advanced_module.textbox.setEnabled(True)
self.use_stdio.setChecked(False)
self.use_stdio.setEnabled(True)
self.use_stdio.stateChanged.connect(self.disable_tcp)
@Slot(bool)
def show_advanced_warning(self, state):
"""
Show a warning when trying to modify the PyLS advanced
settings.
"""
# Don't show warning if the option is already enabled.
# This avoids showing it when the Preferences dialog
# is created.
if self.get_option('advanced/enabled'):
return
# Show warning when toggling the button state
if state:
QMessageBox.warning(
self,
_("Warning"),
_("Modifying these options can break code completion!!"
"
"
"If that's the case, please reset your Spyder preferences "
"by going to the menu"
"
"
"Tools > Reset Spyder to factory defaults"
"
"
"instead of reporting a bug."))
def is_valid(self):
host = self.advanced_host.textbox.text()
# If host is not local, the server must be external
# and we need to automatically check the corresponding
# option
if host not in ['127.0.0.1', 'localhost']:
self.external_server.setChecked(True)
# Checks for external PyLS
if self.external_server.isChecked():
port = int(self.advanced_port.spinbox.text())
# Check that host and port of the current server are
# different from the new ones provided to connect to
# an external server.
lsp = self.plugin.get_provider('lsp')
pyclient = lsp.clients.get('python')
if pyclient is not None:
instance = pyclient['instance']
if (instance is not None and
not pyclient['config']['external']):
if (instance.server_host == host and
instance.server_port == port):
self.report_no_address_change()
return False
# Check connection to LSP server using a TCP socket
response = check_connection_port(host, port)
if not response:
self.report_no_external_server(host, port, 'python')
return False
return True
def report_no_external_server(self, host, port, language):
"""
Report that connection couldn't be established with
an external server.
"""
QMessageBox.critical(
self,
_("Error"),
_("It appears there is no {language} language server listening "
"at address:"
"
"
"{host}:{port}"
"
"
"Please verify that the provided information is correct "
"and try again.").format(host=host, port=port,
language=language.capitalize())
)
def report_no_address_change(self):
"""
Report that server address has no changed after checking the
external server option.
"""
QMessageBox.critical(
self,
_("Error"),
_("The address of the external server you are trying to connect "
"to is the same as the one of the current internal server "
"started by Spyder."
"
"
"Please provide a different address!")
)