# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""
Language Server Protocol configuration tabs.
"""
# Standard library imports
import re
# Third party imports
from qtpy.QtCore import Qt
from qtpy.QtWidgets import (QGroupBox, QGridLayout, QLabel, QMessageBox,
QVBoxLayout, QWidget)
# Local imports
from spyder.api.preferences import SpyderPreferencesTab
from spyder.config.base import _
class FormattingStyleConfigTab(SpyderPreferencesTab):
"""Code style and formatting tab."""
TITLE = _('Code style and formatting')
def __init__(self, parent):
super().__init__(parent)
newcb = self.create_checkbox
pep_url = (
'PEP 8')
code_style_codes_url = _(
"pycodestyle error codes")
code_style_label = QLabel(
_("Spyder can use pycodestyle to analyze your code for "
"conformance to the {} convention. You can also "
"manually show or hide specific warnings by their "
"{}.").format(pep_url, code_style_codes_url))
code_style_label.setOpenExternalLinks(True)
code_style_label.setWordWrap(True)
# Code style checkbox
self.code_style_check = self.create_checkbox(
_("Enable code style linting"),
'pycodestyle')
# Code style options
self.code_style_filenames_match = self.create_lineedit(
_("Only check filenames matching these patterns:"),
'pycodestyle/filename', alignment=Qt.Horizontal, word_wrap=False,
placeholder=_("Check Python files: *.py"))
self.code_style_exclude = self.create_lineedit(
_("Exclude files or directories matching these patterns:"),
'pycodestyle/exclude', alignment=Qt.Horizontal, word_wrap=False,
placeholder=_("Exclude all test files: (?!test_).*\\.py"))
code_style_select = self.create_lineedit(
_("Show the following errors or warnings:").format(
code_style_codes_url),
'pycodestyle/select', alignment=Qt.Horizontal, word_wrap=False,
placeholder=_("Example codes: E113, W391"))
code_style_ignore = self.create_lineedit(
_("Ignore the following errors or warnings:"),
'pycodestyle/ignore', alignment=Qt.Horizontal, word_wrap=False,
placeholder=_("Example codes: E201, E303"))
self.code_style_max_line_length = self.create_spinbox(
_("Maximum allowed line length:"), None,
'pycodestyle/max_line_length', min_=10, max_=500, step=1,
tip=_("Default is 79"))
vertical_line_box = newcb(
_("Show vertical line at that length"), 'edge_line',
section='editor')
# Code style layout
code_style_g_layout = QGridLayout()
code_style_g_layout.addWidget(
self.code_style_filenames_match.label, 1, 0)
code_style_g_layout.addWidget(
self.code_style_filenames_match.textbox, 1, 1)
code_style_g_layout.addWidget(self.code_style_exclude.label, 2, 0)
code_style_g_layout.addWidget(self.code_style_exclude.textbox, 2, 1)
code_style_g_layout.addWidget(code_style_select.label, 3, 0)
code_style_g_layout.addWidget(code_style_select.textbox, 3, 1)
code_style_g_layout.addWidget(code_style_ignore.label, 4, 0)
code_style_g_layout.addWidget(code_style_ignore.textbox, 4, 1)
# Set Code style options enabled/disabled
code_style_g_widget = QWidget()
code_style_g_widget.setLayout(code_style_g_layout)
code_style_g_widget.setEnabled(self.get_option('pycodestyle'))
self.code_style_check.toggled.connect(code_style_g_widget.setEnabled)
# Code style layout
code_style_group = QGroupBox(_("Code style"))
code_style_layout = QVBoxLayout()
code_style_layout.addWidget(code_style_label)
code_style_layout.addWidget(self.code_style_check)
code_style_layout.addWidget(code_style_g_widget)
code_style_group.setLayout(code_style_layout)
# Maximum allowed line length layout
line_length_group = QGroupBox(_("Line length"))
line_length_layout = QVBoxLayout()
line_length_layout.addWidget(self.code_style_max_line_length)
line_length_layout.addWidget(vertical_line_box)
line_length_group.setLayout(line_length_layout)
# Code formatting label
autopep8_url = (
"Autopep8"
)
yapf_url = (
"Yapf"
)
black_url = (
"Black"
)
code_fmt_label = QLabel(
_("Spyder can use {0}, {1} or {2} to format your code for "
"conformance to the {3} convention.").format(
autopep8_url, yapf_url, black_url, pep_url))
code_fmt_label.setOpenExternalLinks(True)
code_fmt_label.setWordWrap(True)
# Code formatting providers
code_fmt_provider = self.create_combobox(
_("Choose the code formatting provider: "),
(("autopep8", 'autopep8'),
("black", 'black')),
'formatting')
# Autoformat on save
format_on_save_box = newcb(
_("Autoformat files on save"),
'format_on_save',
tip=_("If enabled, autoformatting will take place when "
"saving a file"))
# Code formatting layout
code_fmt_group = QGroupBox(_("Code formatting"))
code_fmt_layout = QVBoxLayout()
code_fmt_layout.addWidget(code_fmt_label)
code_fmt_layout.addWidget(code_fmt_provider)
code_fmt_layout.addWidget(format_on_save_box)
code_fmt_group.setLayout(code_fmt_layout)
code_style_fmt_layout = QVBoxLayout()
code_style_fmt_layout.addWidget(code_style_group)
code_style_fmt_layout.addWidget(code_fmt_group)
code_style_fmt_layout.addWidget(line_length_group)
self.setLayout(code_style_fmt_layout)
def report_invalid_regex(self, files=True):
"""
Report that excluded files/directories should be valid regular
expressions.
"""
msg = _('Directory patterns listed for exclusion should be valid '
'regular expressions')
if files:
msg = _('File patterns listed for exclusion should be valid '
'regular expressions')
QMessageBox.critical(self, _("Error"), msg)
def is_valid(self):
# Check regex of code style options
try:
code_style_filenames_matches = (
self.code_style_filenames_match.textbox.text().split(","))
for match in code_style_filenames_matches:
re.compile(match.strip())
except re.error:
self.report_invalid_regex()
return False
try:
code_style_excludes = (
self.code_style_exclude.textbox.text().split(","))
for match in code_style_excludes:
re.compile(match.strip())
except re.error:
self.report_invalid_regex(files=False)
return False
return True