-
Notifications
You must be signed in to change notification settings - Fork 496
/
SqlDelightCopyPasteProcessor.kt
143 lines (130 loc) · 5.02 KB
/
SqlDelightCopyPasteProcessor.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
138
139
140
141
142
143
package app.cash.sqldelight.intellij
import app.cash.sqldelight.core.lang.SqlDelightFile
import app.cash.sqldelight.core.lang.psi.JavaTypeMixin
import app.cash.sqldelight.core.lang.util.findChildOfType
import app.cash.sqldelight.core.psi.SqlDelightImportStmtList
import com.alecstrong.sql.psi.core.psi.SqlCreateTableStmt
import com.alecstrong.sql.psi.core.psi.SqlStmt
import com.alecstrong.sql.psi.core.psi.SqlTypes
import com.intellij.codeInsight.daemon.impl.CollectHighlightsUtil
import com.intellij.codeInsight.editorActions.CopyPastePostProcessor
import com.intellij.codeInsight.editorActions.ReferenceData
import com.intellij.codeInsight.editorActions.ReferenceTransferableData
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.ReadOnlyModificationException
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.parentOfType
import java.awt.datatransfer.Transferable
import java.awt.datatransfer.UnsupportedFlavorException
import java.io.IOException
class SqlDelightCopyPasteProcessor : CopyPastePostProcessor<ReferenceTransferableData>() {
override fun collectTransferableData(
file: PsiFile,
editor: Editor,
startOffsets: IntArray,
endOffsets: IntArray,
): List<ReferenceTransferableData> {
if (file !is SqlDelightFile) {
return emptyList()
}
val result = mutableListOf<ReferenceData>()
var refOffset = 0
for (j in startOffsets.indices) {
refOffset += startOffsets[j]
val elementsInRange = CollectHighlightsUtil.getElementsInRange(file, startOffsets[j], endOffsets[j])
for (element in elementsInRange) {
val data = referenceData(element, refOffset)
if (data != null) {
result += data
}
}
refOffset -= endOffsets[j] + 1
}
return listOf(ReferenceTransferableData(result.toTypedArray()))
}
private fun referenceData(
element: PsiElement,
refOffset: Int,
): ReferenceData? {
if (element !is JavaTypeMixin) {
return null
}
val resolvedClass = element.reference?.resolve() as? PsiClass ?: return null
val qualifiedName = resolvedClass.qualifiedName ?: return null
val range = element.textRange
return ReferenceData(
range.startOffset - refOffset,
range.endOffset - refOffset,
qualifiedName,
null,
)
}
override fun extractTransferableData(content: Transferable): List<ReferenceTransferableData> {
try {
val dataFlavor = ReferenceData.getDataFlavor() ?: return emptyList()
val referenceData = content.getTransferData(dataFlavor) as ReferenceTransferableData?
return listOfNotNull(referenceData)
} catch (ignored: UnsupportedFlavorException) {
} catch (ignored: IOException) {
}
return super.extractTransferableData(content)
}
override fun processTransferableData(
project: Project,
editor: Editor,
bounds: RangeMarker,
caretOffset: Int,
indented: Ref<in Boolean>,
values: MutableList<out ReferenceTransferableData>,
) {
if (DumbService.getInstance(project).isDumb) {
return
}
val references = values.first().data
if (references.isEmpty()) {
return
}
val document = editor.document
val documentManager = PsiDocumentManager.getInstance(project)
val file = documentManager.getPsiFile(document)
if (file !is SqlDelightFile) {
return
}
val elementAtCaret = file.findElementAt(caretOffset)
val insideCreateTableStmt = elementAtCaret?.parentOfType<SqlCreateTableStmt>() != null
val hasCreateTableSibling = elementAtCaret?.parentOfType<SqlStmt>() != null &&
findCreateTableSiblingCatching(elementAtCaret) != null
if (!insideCreateTableStmt && !hasCreateTableSibling) {
return
}
documentManager.commitAllDocuments()
WriteCommandAction.writeCommandAction(project).run<ReadOnlyModificationException> {
val qClassNames = references.map(ReferenceData::qClassName)
val importStmtList = file.findChildOfType<SqlDelightImportStmtList>()?.importStmtList.orEmpty()
val oldImports = importStmtList.map { it.javaType.text }
val newImports = (qClassNames + oldImports).distinct()
.sorted()
.joinToString("\n") { "import $it;" }
if (oldImports.isEmpty()) {
document.insertString(0, "$newImports\n\n")
} else {
val endOffset = importStmtList.maxOfOrNull { it.textOffset + it.textLength } ?: 0
document.replaceString(0, endOffset, newImports)
}
}
}
private fun findCreateTableSiblingCatching(elementAtCaret: PsiElement?): PsiElement? {
return elementAtCaret?.runCatching {
PsiTreeUtil.findSiblingBackward(this, SqlTypes.CREATE_TABLE_STMT, false, null)
}?.getOrNull()
}
}