src/eric7/CodeFormatting/IsortConfigurationDialog.py

branch
eric7
changeset 9453
e5065dde905d
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9452:325c6de4b1f5 9453:e5065dde905d
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 an isort formatting run.
8 """
9
10 import contextlib
11 import copy
12 import pathlib
13
14 from PyQt6.QtCore import pyqtSlot
15 from PyQt6.QtGui import QGuiApplication
16 from PyQt6.QtWidgets import QDialog, QDialogButtonBox
17
18 from eric7.EricWidgets import EricMessageBox
19 from eric7.EricWidgets.EricApplication import ericApp
20
21 from isort import Config
22 from isort.profiles import profiles
23 from isort.settings import VALID_PY_TARGETS
24 from isort.wrap_modes import WrapModes
25
26 import tomlkit
27
28
29 from .Ui_IsortConfigurationDialog import Ui_IsortConfigurationDialog
30
31
32 class IsortConfigurationDialog(QDialog, Ui_IsortConfigurationDialog):
33 """
34 Class implementing a dialog to enter the parameters for an isort formatting run.
35 """
36
37 def __init__(self, withProject=True, onlyProject=False, parent=None):
38 """
39 Constructor
40
41 @param withProject flag indicating to look for project configurations
42 (defaults to True)
43 @type bool (optional)
44 @param onlyProject flag indicating to only look for project configurations
45 (defaults to False)
46 @type bool (optional)
47 @param parent reference to the parent widget (defaults to None)
48 @type QWidget (optional)
49 """
50 super().__init__(parent)
51 self.setupUi(self)
52
53 self.profileComboBox.lineEdit().setClearButtonEnabled(True)
54
55 self.__parameterWidgetMapping = {
56 "profile": self.profileComboBox,
57 "py_version": self.pythonComboBox,
58 "multi_line_output": self.multiLineComboBox,
59 "sort_order": self.sortOrderComboBox,
60 "supported_extensions": self.extensionsEdit,
61 "line_length": self.lineLengthSpinBox,
62 "lines_before_imports": self.linesBeforeImportsSpinBox,
63 "lines_after_imports": self.linesAfterImportsSpinBox,
64 "lines_between_sections": self.linesBetweenSectionsSpinBox,
65 "lines_between_types": self.linesBetweenTypesSpinBox,
66 "include_trailing_comma": self.trailingCommaCheckBox,
67 "use_parentheses": self.parenthesesCheckBox,
68 "sections": self.sectionsEdit,
69 "extend_skip_glob": self.excludeEdit,
70 "case_sensitive": self.sortCaseSensitiveCheckBox,
71 }
72
73 self.__project = (
74 ericApp().getObject("Project") if (withProject or onlyProject) else None
75 )
76 self.__onlyProject = onlyProject
77
78 self.__pyprojectData = {}
79 self.__projectData = {}
80
81 self.__defaultConfig = Config()
82
83 self.__tomlButton = self.buttonBox.addButton(
84 self.tr("Generate TOML"), QDialogButtonBox.ButtonRole.ActionRole
85 )
86 self.__tomlButton.setToolTip(
87 self.tr("Place a code snippet for 'pyproject.toml' into the clipboard.")
88 )
89 self.__tomlButton.clicked.connect(self.__createTomlSnippet)
90
91 self.profileComboBox.addItem("")
92 self.profileComboBox.addItems(sorted(profiles.keys()))
93
94 self.pythonComboBox.addItem("", "")
95 self.pythonComboBox.addItem(self.tr("All Versions"), "all")
96 for pyTarget in VALID_PY_TARGETS:
97 if pyTarget.startswith("3"):
98 self.pythonComboBox.addItem(
99 self.tr("Python {0}").format(pyTarget)
100 if len(pyTarget) == 1
101 else self.tr("Python {0}.{1}").format(pyTarget[0], pyTarget[1:]),
102 pyTarget,
103 )
104
105 self.sortOrderComboBox.addItem("", "")
106 self.sortOrderComboBox.addItem("Natural", "natural")
107 self.sortOrderComboBox.addItem("Native Python", "native")
108
109 self.__populateMultiLineComboBox()
110
111 # setup the source combobox
112 self.sourceComboBox.addItem("", "")
113 if self.__project:
114 pyprojectPath = (
115 pathlib.Path(self.__project.getProjectPath()) / "pyproject.toml"
116 )
117 if pyprojectPath.exists():
118 with contextlib.suppress(tomlkit.exceptions.ParseError, OSError):
119 with pyprojectPath.open("r", encoding="utf-8") as f:
120 data = tomlkit.load(f)
121 config = data.get("tool", {}).get("isort", {})
122 if config:
123 self.__pyprojectData = {
124 k.replace("--", ""): v for k, v in config.items()
125 }
126 self.sourceComboBox.addItem("pyproject.toml", "pyproject")
127 if self.__project.getData("OTHERTOOLSPARMS", "isort") is not None:
128 self.__projectData = copy.deepcopy(
129 self.__project.getData("OTHERTOOLSPARMS", "isort")
130 )
131 self.sourceComboBox.addItem(self.tr("Project File"), "project")
132 elif onlyProject:
133 self.sourceComboBox.addItem(self.tr("Project File"), "project")
134 if not onlyProject:
135 self.sourceComboBox.addItem(self.tr("Defaults"), "default")
136 self.sourceComboBox.addItem(self.tr("Configuration Below"), "dialog")
137
138 if self.__projectData:
139 source = self.__projectData.get("config_source", "")
140 self.sourceComboBox.setCurrentIndex(self.sourceComboBox.findData(source))
141 elif onlyProject:
142 self.sourceComboBox.setCurrentIndex(self.sourceComboBox.findData("project"))
143
144 def __populateMultiLineComboBox(self):
145 """
146 Private method to populate the multi line output selector.
147 """
148 self.multiLineComboBox.addItem("", -1)
149 for entry, wrapMode in (
150 (self.tr("Grid"), WrapModes.GRID),
151 (self.tr("Vertical"), WrapModes.VERTICAL),
152 (self.tr("Hanging Indent"), WrapModes.HANGING_INDENT),
153 (
154 self.tr("Vertical Hanging Indent"),
155 WrapModes.VERTICAL_HANGING_INDENT,
156 ),
157 (self.tr("Hanging Grid"), WrapModes.VERTICAL_GRID),
158 (self.tr("Hanging Grid Grouped"), WrapModes.VERTICAL_GRID_GROUPED),
159 (self.tr("NOQA"), WrapModes.NOQA),
160 (
161 self.tr("Vertical Hanging Indent Bracket"),
162 WrapModes.VERTICAL_HANGING_INDENT_BRACKET,
163 ),
164 (
165 self.tr("Vertical Prefix From Module Import"),
166 WrapModes.VERTICAL_PREFIX_FROM_MODULE_IMPORT,
167 ),
168 (
169 self.tr("Hanging Indent With Parentheses"),
170 WrapModes.HANGING_INDENT_WITH_PARENTHESES,
171 ),
172 (self.tr("Backslash Grid"), WrapModes.BACKSLASH_GRID),
173 ):
174 self.multiLineComboBox.addItem(entry, wrapMode.value)
175
176 def __loadConfiguration(self, confDict):
177 """
178 Private method to load the configuration section with data of the given
179 dictionary.
180
181 Note: Default values will be loaded for missing parameters.
182
183 @param confDict reference to the data to be loaded
184 @type dict
185 """
186 self.pythonComboBox.setCurrentIndex(
187 self.pythonComboBox.findData(
188 str(confDict["py_version"])
189 if "py_version" in confDict
190 else self.__defaultConfig.py_version.replace("py", "")
191 )
192 )
193 self.multiLineComboBox.setCurrentIndex(
194 self.multiLineComboBox.findData(
195 int(confDict["multi_line_output"])
196 if "multi_line_output" in confDict
197 else self.__defaultConfig.multi_line_output.value
198 )
199 )
200 self.sortOrderComboBox.setCurrentIndex(
201 self.sortOrderComboBox.findData(
202 str(confDict["sort_order"])
203 if "sort_order" in confDict
204 else self.__defaultConfig.sort_order
205 )
206 )
207 self.extensionsEdit.setText(
208 " ".join(
209 confDict["supported_extensions"]
210 if "supported_extensions" in confDict
211 else self.__defaultConfig.supported_extensions
212 )
213 )
214 for parameter in (
215 "line_length",
216 "lines_before_imports",
217 "lines_after_imports",
218 "lines_between_sections",
219 "lines_between_types",
220 ):
221 # set spin box values
222 self.__parameterWidgetMapping[parameter].setValue(
223 confDict[parameter]
224 if parameter in confDict
225 else getattr(self.__defaultConfig, parameter)
226 )
227 for parameter in (
228 "include_trailing_comma",
229 "use_parentheses",
230 "case_sensitive",
231 ):
232 # set check box values
233 self.__parameterWidgetMapping[parameter].setChecked(
234 confDict[parameter]
235 if parameter in confDict
236 else getattr(self.__defaultConfig, parameter)
237 )
238 for parameter in (
239 "sections",
240 "extend_skip_glob",
241 ):
242 # set the plain text edits
243 self.__parameterWidgetMapping[parameter].setPlainText(
244 "\n".join(
245 confDict[parameter]
246 if parameter in confDict
247 else getattr(self.__defaultConfig, parameter)
248 )
249 )
250 # set the profile combo box last because it may change other entries
251 self.profileComboBox.setEditText(
252 confDict["profile"]
253 if "profile" in confDict
254 else self.__defaultConfig.profile
255 )
256
257 @pyqtSlot(str)
258 def on_sourceComboBox_currentTextChanged(self, selection):
259 """
260 Private slot to handle the selection of a configuration source.
261
262 @param selection text of the currently selected item
263 @type str
264 """
265 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
266 bool(selection) or self.__onlyProject
267 )
268
269 source = self.sourceComboBox.currentData()
270 if source != "dialog":
271 # reset the profile combo box first
272 self.profileComboBox.setCurrentIndex(0)
273
274 if source == "pyproject":
275 self.__loadConfiguration(self.__pyprojectData)
276 elif source == "project":
277 self.__loadConfiguration(self.__projectData)
278 elif source == "default":
279 self.__loadConfiguration({}) # loads the default values
280 elif source == "dialog":
281 # just leave the current entries
282 pass
283
284 @pyqtSlot(str)
285 def on_profileComboBox_editTextChanged(self, profileName):
286 """
287 Private slot to react upon changes of the selected/entered profile.
288
289 @param profileName name of the current profile
290 @type str
291 """
292 if profileName and profileName in profiles:
293 confDict = self.__getConfigurationDict()
294 confDict["profile"] = profileName
295 confDict.update(profiles[profileName])
296 self.__loadConfiguration(confDict)
297
298 for parameter in self.__parameterWidgetMapping:
299 self.__parameterWidgetMapping[parameter].setEnabled(
300 parameter not in profiles[profileName]
301 )
302 else:
303 for widget in self.__parameterWidgetMapping.values():
304 widget.setEnabled(True)
305
306 @pyqtSlot()
307 def __createTomlSnippet(self):
308 """
309 Private slot to generate a TOML snippet of the current configuration.
310
311 Note: Only non-default values are included in this snippet.
312
313 The code snippet is copied to the clipboard and may be placed inside the
314 'pyproject.toml' file.
315 """
316 configDict = self.__getConfigurationDict()
317
318 isort = tomlkit.table()
319 for key, value in configDict.items():
320 isort[key] = value
321
322 doc = tomlkit.document()
323 doc["tool"] = tomlkit.table(is_super_table=True)
324 doc["tool"]["isort"] = isort
325
326 QGuiApplication.clipboard().setText(tomlkit.dumps(doc))
327
328 EricMessageBox.information(
329 self,
330 self.tr("Create TOML snippet"),
331 self.tr(
332 """The 'pyproject.toml' snippet was copied to the clipboard"""
333 """ successfully."""
334 ),
335 )
336
337 def __getConfigurationDict(self):
338 """
339 Private method to assemble and return a dictionary containing the entered
340 non-default configuration parameters.
341
342 @return dictionary containing the non-default configuration parameters
343 @rtype dict
344 """
345 configDict = {}
346
347 if self.profileComboBox.currentText():
348 configDict["profile"] = self.profileComboBox.currentText()
349 if (
350 self.pythonComboBox.currentText()
351 and self.pythonComboBox.currentData()
352 != self.__defaultConfig.py_version.replace("py", "")
353 ):
354 configDict["py_version"] = self.pythonComboBox.currentData()
355 if self.multiLineComboBox.isEnabled() and self.multiLineComboBox.currentText():
356 configDict["multi_line_output"] = self.multiLineComboBox.currentData()
357 if self.sortOrderComboBox.isEnabled() and self.sortOrderComboBox.currentText():
358 configDict["sort_order"] = self.sortOrderComboBox.currentData()
359 if self.extensionsEdit.isEnabled() and self.extensionsEdit.text():
360 configDict["supported_extensions"] = [
361 e.lstrip(".")
362 for e in self.extensionsEdit.text().strip().split()
363 if e.lstrip(".")
364 ]
365
366 for parameter in (
367 "line_length",
368 "lines_before_imports",
369 "lines_after_imports",
370 "lines_between_sections",
371 "lines_between_types",
372 ):
373 if self.__parameterWidgetMapping[
374 parameter
375 ].isEnabled() and self.__parameterWidgetMapping[
376 parameter
377 ].value() != getattr(
378 self.__defaultConfig, parameter
379 ):
380 configDict[parameter] = self.__parameterWidgetMapping[parameter].value()
381
382 for parameter in (
383 "include_trailing_comma",
384 "use_parentheses",
385 "case_sensitive",
386 ):
387 if self.__parameterWidgetMapping[
388 parameter
389 ].isEnabled() and self.__parameterWidgetMapping[
390 parameter
391 ].isChecked() != getattr(
392 self.__defaultConfig, parameter
393 ):
394 configDict[parameter] = self.__parameterWidgetMapping[
395 parameter
396 ].isChecked()
397
398 for parameter in (
399 "sections",
400 "extend_skip_glob",
401 ):
402 if self.__parameterWidgetMapping[parameter].isEnabled():
403 value = (
404 self.__parameterWidgetMapping[parameter].toPlainText().splitlines()
405 )
406 if value != list(getattr(self.__defaultConfig, parameter)):
407 configDict[parameter] = value
408
409 return configDict
410
411 def getConfiguration(self, saveToProject=False):
412 """
413 Public method to get the current configuration parameters.
414
415 @param saveToProject flag indicating to save the configuration data in the
416 project file (defaults to False)
417 @type bool (optional)
418 @return dictionary containing the configuration parameters
419 @rtype dict
420 """
421 configuration = self.__getConfigurationDict()
422
423 if saveToProject and self.__project:
424 configuration["config_source"] = self.sourceComboBox.currentData()
425 self.__project.setData("OTHERTOOLSPARMS", "isort", configuration)
426
427 return configuration

eric ide

mercurial