1
1
package org.javacs.kt
2
2
3
3
import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange
4
+ import org.jetbrains.kotlin.com.intellij.psi.PsiElement
4
5
import org.jetbrains.kotlin.com.intellij.psi.PsiIdentifier
5
6
import org.javacs.kt.position.changedRegion
6
7
import org.javacs.kt.position.position
@@ -31,7 +32,7 @@ class CompiledFile(
31
32
* Find the type of the expression at `cursor`
32
33
*/
33
34
fun typeAtPoint (cursor : Int ): KotlinType ? {
34
- var cursorExpr = parseAtPoint(cursor)?.findParent<KtExpression >() ? : return nullResult(" Couldn't find expression at ${describePosition(cursor)} " )
35
+ var cursorExpr = parseAtPoint(cursor, asReference = true )?.findParent<KtExpression >() ? : return nullResult(" Couldn't find expression at ${describePosition(cursor)} " )
35
36
val surroundingExpr = expandForType(cursor, cursorExpr)
36
37
val scope = scopeAtPoint(cursor) ? : return nullResult(" Couldn't find scope at ${describePosition(cursor)} " )
37
38
return typeOfExpression(surroundingExpr, scope)
@@ -52,7 +53,7 @@ class CompiledFile(
52
53
}
53
54
54
55
fun referenceAtPoint (cursor : Int ): Pair <KtExpression , DeclarationDescriptor >? {
55
- val element = parseAtPoint(cursor)
56
+ val element = parseAtPoint(cursor, asReference = true )
56
57
var cursorExpr = element?.findParent<KtExpression >() ? : return nullResult(" Couldn't find expression at ${describePosition(cursor)} (only found $element )" )
57
58
val surroundingExpr = expandForReference(cursor, cursorExpr)
58
59
val scope = scopeAtPoint(cursor) ? : return nullResult(" Couldn't find scope at ${describePosition(cursor)} " )
@@ -79,9 +80,13 @@ class CompiledFile(
79
80
}
80
81
81
82
/* *
82
- * Parse the expression at `cursor`
83
+ * Parse the expression at `cursor`.
84
+ *
85
+ * If the `asReference` flag is set, the method will attempt to
86
+ * convert a declaration (e.g. of a class or a function) to a referencing
87
+ * expression before parsing it.
83
88
*/
84
- fun parseAtPoint (cursor : Int ): KtElement ? {
89
+ fun parseAtPoint (cursor : Int , asReference : Boolean = false ): KtElement ? {
85
90
val oldCursor = oldOffset(cursor)
86
91
val oldChanged = changedRegion(parse.text, content)?.first ? : TextRange (cursor, cursor)
87
92
val psi = parse.findElementAt(oldCursor) ? : return nullResult(" Couldn't find anything at ${describePosition(cursor)} " )
@@ -91,40 +96,52 @@ class CompiledFile(
91
96
92
97
LOG .debug { " PSI path: ${psi.parentsWithSelf.toList()} " }
93
98
99
+ val (surroundingContent, offset) = contentAndOffsetFromElement(psi, oldParent, asReference)
100
+ val padOffset = " " .repeat(offset)
101
+ val recompile = classPath.compiler.createFile(padOffset + surroundingContent, Paths .get(" dummy.virtual" + if (isScript) " .kts" else " .kt" ), kind)
102
+ return recompile.findElementAt(cursor)?.findParent<KtElement >()
103
+ }
104
+
105
+ /* *
106
+ * Extracts the surrounding content and the text offset from a
107
+ * PSI element.
108
+ *
109
+ * See `parseAtPoint` for documentation of the `asReference` flag.
110
+ */
111
+ private fun contentAndOffsetFromElement (psi : PsiElement , parent : KtElement , asReference : Boolean ): Pair <String , Int > {
94
112
var surroundingContent: String
95
113
var offset: Int
96
114
97
- if (oldParent is KtClass ) {
98
- // Parsing class, currently only identifiers are supported
99
- if (psi.node.elementType != KtTokens .IDENTIFIER ) return null
100
-
101
- // Parsing class name identifier: Use a fake property with the class name as type
102
- // Otherwise the compiler/analyzer would throw an exception due to a missing TopLevelDescriptorProvider
103
- val recoveryRange = psi.textRange
104
- LOG .info(" Re-parsing {}" , describeRange(recoveryRange, true ))
105
-
106
- val prefix = " val x: "
107
- surroundingContent = prefix + psi.text
108
- offset = recoveryRange.startOffset - prefix.length
109
- } else {
110
- // Parsing some other expression
111
- val recoveryRange = oldParent.textRange
112
- LOG .info(" Re-parsing {}" , describeRange(recoveryRange, true ))
113
-
114
- surroundingContent = content.substring(recoveryRange.startOffset, content.length - (parse.text.length - recoveryRange.endOffset))
115
- offset = recoveryRange.startOffset
116
-
117
- if (! ((oldParent as ? KtParameter )?.hasValOrVar() ? : true )) {
118
- // Prepend 'val' to (e.g. function) parameters
119
- val prefix = " val "
120
- surroundingContent = prefix + surroundingContent
121
- offset - = prefix.length
115
+ if (asReference) {
116
+ // Convert the declaration into a fake reference expression
117
+ when {
118
+ parent is KtClass && psi.node.elementType == KtTokens .IDENTIFIER -> {
119
+ // Converting class name identifier: Use a fake property with the class name as type
120
+ // Otherwise the compiler/analyzer would throw an exception due to a missing TopLevelDescriptorProvider
121
+ val prefix = " val x: "
122
+ surroundingContent = prefix + psi.text
123
+ offset = psi.textRange.startOffset - prefix.length
124
+
125
+ return Pair (surroundingContent, offset)
126
+ }
122
127
}
123
128
}
124
129
125
- val padOffset = " " .repeat(offset)
126
- val recompile = classPath.compiler.createFile(padOffset + surroundingContent, Paths .get(" dummy.virtual" + if (isScript) " .kts" else " .kt" ), kind)
127
- return recompile.findElementAt(cursor)?.findParent<KtElement >()
130
+ // Otherwise just use the expression
131
+ val recoveryRange = parent.textRange
132
+ LOG .info(" Re-parsing {}" , describeRange(recoveryRange, true ))
133
+
134
+ surroundingContent = content.substring(recoveryRange.startOffset, content.length - (parse.text.length - recoveryRange.endOffset))
135
+ offset = recoveryRange.startOffset
136
+
137
+ if (asReference && ! ((parent as ? KtParameter )?.hasValOrVar() ? : true )) {
138
+ // Prepend 'val' to (e.g. function) parameters
139
+ val prefix = " val "
140
+ surroundingContent = prefix + surroundingContent
141
+ offset - = prefix.length
142
+ }
143
+
144
+ return Pair (surroundingContent, offset)
128
145
}
129
146
130
147
/* *
0 commit comments