/
FregeAddImportQuickFix.kt
137 lines (124 loc) · 5.1 KB
/
FregeAddImportQuickFix.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package com.plugin.frege.inspections
import com.intellij.codeInsight.navigation.NavigationUtil
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.ide.DataManager
import com.intellij.lang.ASTNode
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.parentOfType
import com.intellij.ui.ColoredListCellRenderer
import com.plugin.frege.psi.*
import com.plugin.frege.psi.mixin.FregeProgramUtil.imports
import com.plugin.frege.psi.util.FregeName
import com.plugin.frege.stubs.index.FregeMethodNameIndex
import com.plugin.frege.stubs.index.FregeShortClassNameIndex
import javax.swing.JList
class FregeAddImportQuickFix : LocalQuickFix {
override fun getFamilyName(): String = "Import module"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
descriptor.psiElement?.let { usage ->
if (usage !is FregeCompositeElement || usage is FregeNamedElement) {
return
}
usage.parentOfType<FregeProgram>()?.let { module ->
val name = FregeName(usage)
val candidates = findCandidates(name, project)
showPromptToPickCandidate(module, name, candidates, project)
}
}
}
private fun findCandidates(name: FregeName, project: Project): List<FregeProgram> {
val methods = FregeMethodNameIndex.findByName(
name.shortName, project, GlobalSearchScope.everythingScope(project)
).mapNotNull { it.parentOfType<FregeProgram>(true) }
val classes = FregeShortClassNameIndex.findByName(
name.shortName, project, GlobalSearchScope.everythingScope(project)
).mapNotNull { it.parentOfType<FregeProgram>(true) }
return (methods + classes).distinct()
}
private fun showPromptToPickCandidate(
module: FregeProgram,
name: FregeName,
candidates: List<FregeProgram>,
project: Project
) {
if (candidates.isEmpty()) {
return
}
DataManager.getInstance().dataContextFromFocusAsync.onSuccess { dataContext ->
val picker = ImportPickerUI(dataContext, name, project)
picker.pick(candidates) { candidate ->
WriteCommandAction.runWriteCommandAction(project) {
ImportAdder.addImport(module, candidate)
}
}
}
}
}
private class ImportPickerUI(
private val dataContext: DataContext,
private val name: FregeName,
private val project: Project
) {
fun pick(candidates: List<FregeProgram>, callback: (FregeProgram) -> Unit) {
val popup = JBPopupFactory.getInstance().createPopupChooserBuilder(candidates)
.setTitle("Import '${name.shortName}' from module:")
.setItemChosenCallback { callback(it) }
.setNamerForFiltering { it.qualifiedName }
.setRenderer(CandidateRenderer())
.createPopup()
NavigationUtil.hidePopupIfDumbModeStarts(popup, project)
popup.showInBestPositionFor(dataContext)
}
}
private class CandidateRenderer : ColoredListCellRenderer<FregeProgram>() {
override fun customizeCellRenderer(
list: JList<out FregeProgram>,
value: FregeProgram,
index: Int,
selected: Boolean,
hasFocus: Boolean
) {
value.qualifiedName?.let { append(it) }
}
}
private object ImportAdder {
fun addImport(toModule: FregeProgram, moduleToAdd: FregeProgram) {
val qualifiedName = moduleToAdd.qualifiedName ?: return
val project = toModule.project
val newImport = FregeElementFactory.createTopDecl(project, "import $qualifiedName").node
val lastImport = toModule.imports.lastOrNull()
val newLine = FregeElementFactory.createNewLine(project).node
if (lastImport != null) {
val position = getInsertPositionAfterImport(lastImport)
doInsert(position, newLine, newImport)
} else {
val position = getInsertPositionForFirstImport(toModule)
val newLine2 = FregeElementFactory.createNewLine(project).node
doInsert(position, newLine, newLine2, newImport)
}
}
private fun getInsertPositionAfterImport(import: FregeImportDecl): ASTNode {
val topDecl = import.parentOfType<FregeTopDecl>()!!
var node = topDecl.node
while (!node.textContains('\n')) {
node = node.treeNext!!
}
return node.treeNext!!
}
private fun getInsertPositionForFirstImport(module: FregeProgram): ASTNode {
return module.body?.firstChild?.node!!
}
private fun doInsert(insertPosition: ASTNode, vararg elements: ASTNode) {
var lastNode = insertPosition
val parentNode = lastNode.treeParent
for (element in elements) {
parentNode.addChild(element, lastNode)
lastNode = element
}
}
}