src/eric7/CodeFormatting/BlackConfigurationDialog.py

branch
eric7
changeset 9214
bd28e56047d7
child 9216
e89083501ce3
equal deleted inserted replaced
9213:2bf743848d2f 9214:bd28e56047d7
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to enter the parameters for a Black formatting run.
8 """
9
10 import contextlib
11 import copy
12 import pathlib
13
14 import black
15 import tomlkit
16
17 from PyQt6.QtCore import pyqtSlot, Qt
18 from PyQt6.QtGui import QFontMetricsF, QGuiApplication
19 from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QListWidgetItem
20
21 from EricWidgets import EricMessageBox
22 from EricWidgets.EricApplication import ericApp
23
24 from .Ui_BlackConfigurationDialog import Ui_BlackConfigurationDialog
25
26 from . import BlackUtilities
27
28
29 class BlackConfigurationDialog(QDialog, Ui_BlackConfigurationDialog):
30 """
31 Class implementing a dialog to enter the parameters for a Black formatting run.
32 """
33 def __init__(self, withProject=True, parent=None):
34 """
35 Constructor
36
37 @param withProject flag indicating to look for project configurations
38 (defaults to True)
39 @type bool
40 @param parent reference to the parent widget (defaults to None)
41 @type QWidget (optional)
42 """
43 super().__init__(parent)
44 self.setupUi(self)
45
46 self.__project = ericApp().getObject("Project") if withProject else None
47
48 indentTabWidth = (
49 QFontMetricsF(self.excludeEdit.font()).horizontalAdvance(" ") * 2
50 )
51 self.excludeEdit.document().setIndentWidth(indentTabWidth)
52 self.excludeEdit.setTabStopDistance(indentTabWidth)
53
54 self.__pyprojectData = {}
55 self.__projectData = {}
56
57 self.__tomlButton = self.buttonBox.addButton(
58 self.tr("Generate TOML"),
59 QDialogButtonBox.ButtonRole.ActionRole
60 )
61 self.__tomlButton.setToolTip(self.tr(
62 "Place a code snippet for 'pyproject.toml' into the clipboard."
63 ))
64 self.__tomlButton.clicked.connect(self.__createTomlSnippet)
65
66 # setup the source combobox
67 self.sourceComboBox.addItem("", "")
68 if self.__project:
69 pyprojectPath = pathlib.Path(
70 self.__project.getProjectPath()
71 ) / "pyproject.toml"
72 if pyprojectPath.exists():
73 with contextlib.suppress(tomlkit.exceptions.ParseError, OSError):
74 with pyprojectPath.open("r", encoding="utf-8") as f:
75 data = tomlkit.load(f)
76 config = data.get("tool", {}).get("black", {})
77 if config:
78 self.__pyprojectData = {
79 k.replace("--", "").replace("-", "_"): v
80 for k, v in config.items()
81 }
82 self.sourceComboBox.addItem("pyproject.toml", "pyproject")
83 if self.__project.getData("OTHERTOOLSPARMS", "Black") is not None:
84 self.__projectData = copy.deepcopy(
85 self.__project.getData("OTHERTOOLSPARMS", "Black")
86 )
87 self.sourceComboBox.addItem(self.tr("Project File"), "project")
88 self.sourceComboBox.addItem(self.tr("Defaults"), "default")
89 self.sourceComboBox.addItem(self.tr("Configuration Below"), "dialog")
90
91 self.__populateTargetVersionsList()
92
93 if self.__projectData:
94 source = self.__projectData.get("source", "")
95 self.sourceComboBox.setCurrentIndex(self.sourceComboBox.findData(source))
96
97 def __populateTargetVersionsList(self):
98 """
99 Private method to populate the target versions list widget with checkable
100 Python version entries.
101 """
102 targets = [
103 (int(t[2]), int(t[3:]), t)
104 for t in dir(black.TargetVersion)
105 if t.startswith("PY")
106 ]
107 for target in sorted(targets):
108 itm = QListWidgetItem(
109 "Python {0}.{1}".format(target[0], target[1]), self.targetVersionsList
110 )
111 itm.setData(Qt.ItemDataRole.UserRole, target[2])
112 itm.setFlags(itm.flags() | Qt.ItemFlag.ItemIsUserCheckable)
113 itm.setCheckState(Qt.CheckState.Unchecked)
114
115 def __loadConfiguration(self, configurationDict):
116 """
117 Private method to load the configuration section with data of the given
118 dictionary.
119
120 @param configurationDict reference to the data to be loaded
121 @type dict
122 """
123 confDict = copy.deepcopy(BlackUtilities.getDefaultConfiguration())
124 confDict.update(configurationDict)
125
126 self.lineLengthSpinBox.setValue(int(confDict["line-length"]))
127 self.skipStringNormalCheckBox.setChecked(confDict["skip-string-normalization"])
128 self.skipMagicCommaCheckBox.setChecked(confDict["skip-magic-trailing-comma"])
129 self.excludeEdit.setPlainText(confDict["extend-exclude"])
130 for row in range(self.targetVersionsList.count()):
131 itm = self.targetVersionsList.item(row)
132 itm.setCheckState(
133 Qt.CheckState.Checked
134 if itm.data(Qt.ItemDataRole.UserRole).lower()
135 in confDict["target-version"] else
136 Qt.CheckState.Unchecked
137 )
138
139 @pyqtSlot(str)
140 def on_sourceComboBox_currentTextChanged(self, selection):
141 """
142 Private slot to handle the selection of a configuration source.
143
144 @param selection text of the currently selected item
145 @type str
146 """
147 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
148 bool(selection)
149 )
150
151 source = self.sourceComboBox.currentData()
152 if source == "pyproject":
153 self.__loadConfiguration(self.__pyprojectData)
154 elif source == "project":
155 self.__loadConfiguration(self.__projectData)
156 elif source == "default":
157 self.__loadConfiguration(BlackUtilities.getDefaultConfiguration())
158 elif source == "dialog":
159 # just leave the current entries
160 pass
161
162 @pyqtSlot()
163 def on_excludeEdit_textChanged(self):
164 """
165 Private slot to enable the validate button depending on the exclude text.
166 """
167 self.validateButton.setEnabled(bool(self.excludeEdit.toPlainText()))
168
169 @pyqtSlot()
170 def on_validateButton_clicked(self):
171 """
172 Private slot to validate the entered exclusion regular expression.
173 """
174 regexp = self.excludeEdit.toPlainText()
175 valid, error = BlackUtilities.validateRegExp(regexp)
176 if valid:
177 EricMessageBox.information(
178 self,
179 self.tr("Validation"),
180 self.tr("""The exclusion expression is valid.""")
181 )
182 else:
183 EricMessageBox.critical(
184 self,
185 self.tr("Validation Error"),
186 error
187 )
188
189 def __getTargetList(self):
190 """
191 Private method to get the list of checked target versions.
192
193 @return list of target versions
194 @rtype list of str
195 """
196 targets = []
197 for row in range(self.targetVersionsList.count()):
198 itm = self.targetVersionsList.item(row)
199 if itm.checkState() == Qt.CheckState.Checked:
200 targets.append(itm.data(Qt.ItemDataRole.UserRole).lower())
201
202 return targets
203
204 @pyqtSlot()
205 def __createTomlSnippet(self):
206 """
207 Private slot to generate a TOML snippet of the current configuration.
208
209 Note: Only non-default values are included in this snippet.
210
211 The code snippet is copied to the clipboard and may be placed inside the
212 'pyproject.toml' file.
213 """
214 doc = tomlkit.document()
215
216 black = tomlkit.table()
217 targetList = self.__getTargetList()
218 if targetList:
219 black["target-version"] = targetList
220 black["line-length"] = self.lineLengthSpinBox.value()
221 if self.skipStringNormalCheckBox.isChecked():
222 black["skip-string-normalization"] = True
223 if self.skipMagicCommaCheckBox.isChecked():
224 black["skip-magic-trailing-comma"] = True
225
226 excludeRegexp = self.excludeEdit.toPlainText()
227 if excludeRegexp and BlackUtilities.validateRegExp(excludeRegexp)[0]:
228 black["extend-exclude"] = tomlkit.string(
229 "\n{0}\n".format(excludeRegexp.strip()),
230 literal=True,
231 multiline=True
232 )
233
234 doc["tool"] = tomlkit.table(is_super_table=True)
235 doc["tool"]["black"] = black
236
237 QGuiApplication.clipboard().setText(tomlkit.dumps(doc))
238
239 EricMessageBox.information(
240 self,
241 self.tr("Create TOML snipper"),
242 self.tr("""The 'pyproject.toml' snippet was copied to the clipboard"""
243 """ successfully.""")
244 )
245
246 def getConfiguration(self):
247 """
248 Public method to get the current configuration parameters.
249
250 @return dictionary containing the configuration parameters
251 @rtype dict
252 """
253 configuration = BlackUtilities.getDefaultConfiguration()
254
255 configuration["source"] = self.sourceComboBox.currentData()
256 configuration["target-version"] = self.__getTargetList()
257 configuration["line-length"] = self.lineLengthSpinBox.value()
258 configuration["skip-string-normalization"] = (
259 self.skipStringNormalCheckBox.isChecked()
260 )
261 configuration["skip-magic-trailing-comma"] = (
262 self.skipMagicCommaCheckBox.isChecked()
263 )
264 configuration["extend-exclude"] = self.excludeEdit.toPlainText().strip()
265
266 if self.__project:
267 self.__project.setData("OTHERTOOLSPARMS", "Black", configuration)
268
269 return configuration

eric ide

mercurial