Skip to content

Commit acaaa0b

Browse files
committed
Add customizable settings panel and symbol mappings support
Introduced a settings UI for configuring custom symbol mappings in the Prettify Python plugin. The mappings can now be defined, reset, or updated via the settings panel, and changes are applied dynamically across open files. Refactored folding logic to use user-defined settings instead of hardcoded values.
1 parent c88e19d commit acaaa0b

File tree

8 files changed

+304
-31
lines changed

8 files changed

+304
-31
lines changed

src/main/kotlin/dev/meanmail/prettifypython/FoldingBuilder.kt

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,14 @@ import com.intellij.lang.folding.FoldingBuilder
55
import com.intellij.lang.folding.FoldingDescriptor
66
import com.intellij.openapi.editor.Document
77
import com.intellij.openapi.util.TextRange
8-
import com.intellij.psi.PsiElement
98
import com.intellij.psi.impl.source.tree.LeafPsiElement
109
import com.jetbrains.python.psi.PyBinaryExpression
1110
import com.jetbrains.python.psi.PyStringElement
11+
import dev.meanmail.prettifypython.settings.PrettifySettings
1212

1313

1414
class PrettifyFoldingBuilder : FoldingBuilder {
1515

16-
private val prettySymbolMaps = hashMapOf(
17-
">=" to { "" },
18-
"<=" to { "" },
19-
"!=" to { "" },
20-
"->" to { "" },
21-
"lambda" to { "λ" },
22-
"**" to { node: PsiElement ->
23-
if (node.parent is PyBinaryExpression) "^" else null
24-
}
25-
)
26-
2716
private fun getDescriptorsForChildren(node: ASTNode): List<FoldingDescriptor> {
2817
val descriptors = mutableListOf<FoldingDescriptor>()
2918

@@ -46,16 +35,23 @@ class PrettifyFoldingBuilder : FoldingBuilder {
4635
val descriptors = mutableListOf<FoldingDescriptor>()
4736
val text = node.text
4837

49-
val replacerCall = prettySymbolMaps.getOrDefault(text, null)
50-
?: return emptyList()
38+
val settings = PrettifySettings.getInstance()
39+
val replacement = settings.symbolMappings[text]
40+
41+
if (replacement == null || (text == "**" && node.psi.parent !is PyBinaryExpression)) {
42+
return emptyList()
43+
}
5144

52-
val replacer = replacerCall(node) ?: return emptyList()
5345
val nodeRange = node.textRange
54-
val range = TextRange.create(nodeRange.startOffset,
55-
nodeRange.endOffset)
46+
val range = TextRange.create(
47+
nodeRange.startOffset,
48+
nodeRange.endOffset
49+
)
5650
descriptors.add(
57-
PrettifyFoldingDescriptor(node, range, null,
58-
replacer, true)
51+
PrettifyFoldingDescriptor(
52+
node, range, null,
53+
replacement, true
54+
)
5955
)
6056

6157
return descriptors

src/main/kotlin/dev/meanmail/prettifypython/PrettifyFoldingDescriptor.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import com.intellij.openapi.editor.FoldingGroup
55
import com.intellij.openapi.util.TextRange
66
import com.intellij.psi.PsiElement
77

8-
class PrettifyFoldingDescriptor(node: PsiElement,
9-
range: TextRange,
10-
group: FoldingGroup?,
11-
name: String,
12-
private val notExpandable: Boolean) :
13-
FoldingDescriptor(node.node, range, group, name) {
8+
class PrettifyFoldingDescriptor(
9+
node: PsiElement,
10+
range: TextRange,
11+
group: FoldingGroup?,
12+
name: String,
13+
private val notExpandable: Boolean
14+
) :
15+
FoldingDescriptor(node.node, range, group, name) {
1416

1517
override fun isNonExpandable() = notExpandable
1618
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package dev.meanmail.prettifypython.settings
2+
3+
import com.intellij.openapi.application.ApplicationManager
4+
import com.intellij.openapi.components.PersistentStateComponent
5+
import com.intellij.openapi.components.State
6+
import com.intellij.openapi.components.Storage
7+
8+
@State(
9+
name = "PrettifySettings",
10+
storages = [Storage("PrettifySettings.xml")]
11+
)
12+
class PrettifySettings : PersistentStateComponent<PrettifySettings.State> {
13+
companion object {
14+
fun getInstance(): PrettifySettings =
15+
ApplicationManager.getApplication().getService(PrettifySettings::class.java)
16+
17+
val DEFAULT_MAPPINGS = mapOf(
18+
">=" to "",
19+
"<=" to "",
20+
"!=" to "",
21+
"->" to "",
22+
"lambda" to "λ",
23+
"**" to "^"
24+
)
25+
}
26+
27+
data class State(
28+
var symbolMappings: MutableMap<String, String> = DEFAULT_MAPPINGS.toMutableMap()
29+
)
30+
31+
private var myState = State()
32+
33+
override fun getState(): State = myState
34+
35+
override fun loadState(state: State) {
36+
myState = state
37+
}
38+
39+
var symbolMappings: Map<String, String>
40+
get() = myState.symbolMappings
41+
set(value) {
42+
myState.symbolMappings = value.toMutableMap()
43+
}
44+
45+
fun resetToDefaults() {
46+
symbolMappings = DEFAULT_MAPPINGS
47+
}
48+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package dev.meanmail.prettifypython.settings
2+
3+
import com.intellij.ui.IdeBorderFactory
4+
import com.intellij.ui.components.JBLabel
5+
import com.intellij.ui.components.JBScrollPane
6+
import com.intellij.ui.components.JBTextField
7+
import com.intellij.util.ui.JBUI
8+
import java.awt.BorderLayout
9+
import java.awt.Dimension
10+
import java.awt.GridBagConstraints
11+
import java.awt.GridBagLayout
12+
import javax.swing.JButton
13+
import javax.swing.JLabel
14+
import javax.swing.JPanel
15+
import javax.swing.JTextField
16+
17+
class PrettifySettingsComponent {
18+
private val mappingFields = mutableMapOf<JTextField, JTextField>()
19+
private val mainPanel: JPanel
20+
private val mappingsPanel: JPanel
21+
22+
init {
23+
mappingsPanel = JPanel(GridBagLayout())
24+
val settings = PrettifySettings.getInstance()
25+
26+
mainPanel = JPanel(BorderLayout())
27+
mainPanel.border = JBUI.Borders.empty(10)
28+
29+
val titleLabel = JLabel("Symbol Mappings")
30+
titleLabel.border = JBUI.Borders.empty(0, 0, 5, 0)
31+
mainPanel.add(titleLabel, BorderLayout.NORTH)
32+
33+
val constraints = GridBagConstraints().apply {
34+
fill = GridBagConstraints.HORIZONTAL
35+
weightx = 1.0
36+
}
37+
38+
// Headers
39+
constraints.gridy = 0
40+
constraints.gridx = 0
41+
mappingsPanel.add(JBLabel("Original").apply {
42+
border = JBUI.Borders.empty(0, 0, 5, 10)
43+
}, constraints)
44+
45+
constraints.gridx = 1
46+
mappingsPanel.add(JBLabel("Replacement").apply {
47+
border = JBUI.Borders.empty(0, 0, 5, 10)
48+
}, constraints)
49+
50+
constraints.gridx = 2
51+
mappingsPanel.add(JBLabel(""), constraints)
52+
53+
// Add mapping fields
54+
var row = 1
55+
settings.symbolMappings.forEach { (original, replacement) ->
56+
constraints.gridy = row
57+
addMappingRow(constraints, original, replacement)
58+
row++
59+
}
60+
61+
// Add and Reset buttons panel
62+
val buttonPanel = JPanel(BorderLayout())
63+
64+
val addButton = JButton("Add Mapping")
65+
addButton.addActionListener {
66+
constraints.gridy = mappingFields.size + 1
67+
addMappingRow(constraints)
68+
mainPanel.revalidate()
69+
mainPanel.repaint()
70+
}
71+
72+
val resetButton = JButton("Reset All to Defaults")
73+
resetButton.addActionListener {
74+
setMappings(PrettifySettings.DEFAULT_MAPPINGS)
75+
}
76+
77+
buttonPanel.add(addButton, BorderLayout.WEST)
78+
buttonPanel.add(resetButton, BorderLayout.EAST)
79+
80+
val scrollPane = JBScrollPane(mappingsPanel)
81+
scrollPane.border = IdeBorderFactory.createEmptyBorder()
82+
mainPanel.add(scrollPane, BorderLayout.CENTER)
83+
mainPanel.add(buttonPanel, BorderLayout.SOUTH)
84+
}
85+
86+
private fun addMappingRow(constraints: GridBagConstraints, original: String = "", replacement: String = "") {
87+
// Original field
88+
constraints.gridx = 0
89+
val originalField = JBTextField(original)
90+
originalField.preferredSize = Dimension(100, originalField.preferredSize.height)
91+
mappingsPanel.add(originalField, constraints)
92+
93+
// Replacement field
94+
constraints.gridx = 1
95+
val replacementField = JBTextField(replacement)
96+
replacementField.preferredSize = Dimension(100, replacementField.preferredSize.height)
97+
mappingsPanel.add(replacementField, constraints)
98+
99+
// Delete button
100+
constraints.gridx = 2
101+
val deleteButton = JButton("Delete")
102+
deleteButton.addActionListener {
103+
mappingFields.remove(originalField)
104+
mappingsPanel.remove(originalField)
105+
mappingsPanel.remove(replacementField)
106+
mappingsPanel.remove(deleteButton)
107+
mainPanel.revalidate()
108+
mainPanel.repaint()
109+
}
110+
mappingsPanel.add(deleteButton, constraints)
111+
112+
mappingFields[originalField] = replacementField
113+
}
114+
115+
val panel: JPanel get() = mainPanel
116+
117+
fun getMappings(): Map<String, String> {
118+
return mappingFields.entries
119+
.filter { it.key.text.isNotBlank() }
120+
.associate { it.key.text to it.value.text }
121+
}
122+
123+
fun setMappings(mappings: Map<String, String>) {
124+
mappingFields.clear()
125+
mappingsPanel.removeAll()
126+
127+
val constraints = GridBagConstraints().apply {
128+
fill = GridBagConstraints.HORIZONTAL
129+
weightx = 1.0
130+
}
131+
132+
// Headers
133+
constraints.gridy = 0
134+
constraints.gridx = 0
135+
mappingsPanel.add(JBLabel("Original").apply {
136+
border = JBUI.Borders.empty(0, 0, 5, 10)
137+
}, constraints)
138+
139+
constraints.gridx = 1
140+
mappingsPanel.add(JBLabel("Replacement").apply {
141+
border = JBUI.Borders.empty(0, 0, 5, 10)
142+
}, constraints)
143+
144+
constraints.gridx = 2
145+
mappingsPanel.add(JBLabel(""), constraints)
146+
147+
// Add mapping fields
148+
var row = 1
149+
mappings.forEach { (original, replacement) ->
150+
constraints.gridy = row
151+
addMappingRow(constraints, original, replacement)
152+
row++
153+
}
154+
155+
mainPanel.revalidate()
156+
mainPanel.repaint()
157+
}
158+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package dev.meanmail.prettifypython.settings
2+
3+
import com.intellij.openapi.application.ApplicationManager
4+
import com.intellij.openapi.fileEditor.FileEditorManager
5+
import com.intellij.openapi.fileEditor.TextEditor
6+
import com.intellij.openapi.options.Configurable
7+
import com.intellij.openapi.project.ProjectManager
8+
import com.intellij.openapi.vfs.VirtualFile
9+
import com.intellij.util.FileContentUtilCore
10+
import javax.swing.JComponent
11+
12+
class PrettifySettingsConfigurable : Configurable {
13+
private var settingsComponent: PrettifySettingsComponent? = null
14+
15+
override fun getDisplayName(): String = "Prettify Python"
16+
17+
override fun createComponent(): JComponent {
18+
settingsComponent = PrettifySettingsComponent()
19+
return settingsComponent!!.panel
20+
}
21+
22+
override fun isModified(): Boolean {
23+
val settings = PrettifySettings.getInstance()
24+
return settingsComponent?.getMappings() != settings.symbolMappings
25+
}
26+
27+
override fun apply() {
28+
val settings = PrettifySettings.getInstance()
29+
settings.symbolMappings = settingsComponent?.getMappings() ?: mutableMapOf()
30+
31+
// Update all open files
32+
ApplicationManager.getApplication().invokeLater {
33+
val filesToReparse = mutableListOf<VirtualFile>()
34+
35+
ProjectManager.getInstance().openProjects.forEach { project ->
36+
FileEditorManager.getInstance(project).allEditors.forEach { editor ->
37+
if (editor is TextEditor) {
38+
editor.file?.let { filesToReparse.add(it) }
39+
}
40+
}
41+
}
42+
43+
FileContentUtilCore.reparseFiles(filesToReparse)
44+
}
45+
}
46+
47+
override fun reset() {
48+
val settings = PrettifySettings.getInstance()
49+
settingsComponent?.setMappings(settings.symbolMappings)
50+
}
51+
52+
override fun disposeUIResources() {
53+
settingsComponent = null
54+
}
55+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,16 @@
1010
<depends>com.intellij.modules.lang</depends>
1111
<depends optional="true" config-file="python-config.xml">com.intellij.modules.python</depends>
1212

13+
<extensions defaultExtensionNs="com.intellij">
14+
<lang.foldingBuilder language="Python"
15+
implementationClass="dev.meanmail.prettifypython.PrettifyFoldingBuilder"/>
16+
<applicationService
17+
serviceImplementation="dev.meanmail.prettifypython.settings.PrettifySettings"/>
18+
<applicationConfigurable
19+
parentId="editor"
20+
instance="dev.meanmail.prettifypython.settings.PrettifySettingsConfigurable"
21+
id="dev.meanmail.prettifypython.settings.PrettifySettingsConfigurable"
22+
displayName="Prettify Python"/>
23+
</extensions>
24+
1325
</idea-plugin>
Lines changed: 4 additions & 3 deletions
Loading
Lines changed: 4 additions & 3 deletions
Loading

0 commit comments

Comments
 (0)