@@ -20,6 +20,7 @@ import com.intellij.debugger.DebuggerManagerEx
20
20
import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
21
21
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
22
22
import com.intellij.debugger.engine.evaluation.TextWithImports
23
+ import com.intellij.debugger.engine.events.DebuggerCommandImpl
23
24
import com.intellij.debugger.impl.DebuggerContextImpl
24
25
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
25
26
import com.intellij.ide.highlighter.JavaFileType
@@ -37,9 +38,7 @@ import com.intellij.util.concurrency.Semaphore
37
38
import com.intellij.xdebugger.XDebuggerManager
38
39
import com.intellij.xdebugger.impl.XDebugSessionImpl
39
40
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
40
- import com.sun.jdi.ArrayReference
41
- import com.sun.jdi.PrimitiveValue
42
- import com.sun.jdi.Value
41
+ import com.sun.jdi.*
43
42
import org.jetbrains.annotations.TestOnly
44
43
import org.jetbrains.eval4j.jdi.asValue
45
44
import org.jetbrains.kotlin.asJava.classes.KtLightClass
@@ -51,6 +50,7 @@ import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
51
50
import org.jetbrains.kotlin.idea.refactoring.j2kText
52
51
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
53
52
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
53
+ import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass
54
54
import org.jetbrains.kotlin.j2k.AfterConversionPass
55
55
import org.jetbrains.kotlin.psi.*
56
56
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
@@ -117,9 +117,86 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
117
117
}
118
118
})
119
119
120
+ if (contextElement != null && contextElement !is KtElement ) {
121
+ codeFragment.putCopyableUserData(KtCodeFragment .FAKE_CONTEXT_FOR_JAVA_FILE , {
122
+ val emptyFile = createFakeFileWithJavaContextElement(" " , contextElement)
123
+
124
+ val debuggerContext = DebuggerManagerEx .getInstanceEx(project).context
125
+ val debuggerSession = debuggerContext.debuggerSession
126
+ if ((debuggerSession == null || debuggerContext.suspendContext == null ) && ! ApplicationManager .getApplication().isUnitTestMode) {
127
+ LOG .warn(" Couldn't create fake context element for java file, debugger isn't paused on breakpoint" )
128
+ return @putCopyableUserData emptyFile
129
+ }
130
+
131
+ // TODO: 'this' is unavailable
132
+ val visibleVariables = getVisibleLocalVariables(contextElement, debuggerContext)
133
+ if (visibleVariables == null ) {
134
+ LOG .warn(" Couldn't get a list of local variables for ${debuggerContext.sourcePosition.file.name} :${debuggerContext.sourcePosition.line} " )
135
+ return @putCopyableUserData emptyFile
136
+ }
137
+
138
+ val fakeFunctionText = StringBuilder ().apply {
139
+ append(" fun _java_locals_debug_fun_() {\n " )
140
+
141
+ val psiNameHelper = PsiNameHelper .getInstance(project)
142
+ visibleVariables.forEach {
143
+ val variable = it.key
144
+ val variableName = variable.name()
145
+ if (! psiNameHelper.isIdentifier(variableName)) return @forEach
146
+
147
+ val kotlinProperty = createKotlinProperty(project, variableName, variable.type().name(), it.value) ? : return @forEach
148
+ append(" $kotlinProperty \n " )
149
+ }
150
+
151
+ append(" val _debug_context_val = 1\n " )
152
+ append(" }" )
153
+ }.toString()
154
+
155
+ val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
156
+ val fakeFunction = fakeFile.declarations.firstOrNull() as ? KtFunction
157
+ val fakeContext = (fakeFunction?.bodyExpression as ? KtBlockExpression )?.statements?.lastOrNull()
158
+
159
+ return @putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ? : emptyFile
160
+ })
161
+ }
162
+
120
163
return codeFragment
121
164
}
122
165
166
+ private fun getVisibleLocalVariables (contextElement : PsiElement ? , debuggerContext : DebuggerContextImpl ): Map <LocalVariable , Value >? {
167
+ val semaphore = Semaphore ()
168
+ semaphore.down()
169
+
170
+ var visibleVariables: Map <LocalVariable , Value >? = null
171
+
172
+ val worker = object : DebuggerCommandImpl () {
173
+ override fun action () {
174
+ try {
175
+ val frame = if (ApplicationManager .getApplication().isUnitTestMode)
176
+ contextElement?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS )?.frameProxy?.stackFrame
177
+ else
178
+ debuggerContext.frameProxy?.stackFrame
179
+
180
+ visibleVariables = frame?.let { it.getValues(it.visibleVariables()) } ? : emptyMap<LocalVariable , Value >()
181
+ }
182
+ catch (ignored: AbsentInformationException ) {
183
+ // Debug info unavailable
184
+ }
185
+ finally {
186
+ semaphore.up()
187
+ }
188
+ }
189
+ }
190
+
191
+ debuggerContext.debugProcess?.managerThread?.invoke(worker)
192
+
193
+ for (i in 0 .. 50 ) {
194
+ if (semaphore.waitFor(20 )) break
195
+ }
196
+
197
+ return visibleVariables
198
+ }
199
+
123
200
private fun initImports (imports : String? ): String? {
124
201
if (imports != null && ! imports.isEmpty()) {
125
202
return imports.split(KtCodeFragment .IMPORT_SEPARATOR )
@@ -142,8 +219,11 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
142
219
return import
143
220
}
144
221
145
- private fun getWrappedContextElement (project : Project , context : PsiElement ? )
146
- = wrapContextIfNeeded(project, context, getContextElement(context))
222
+ private fun getWrappedContextElement (project : Project , context : PsiElement ? ): PsiElement ? {
223
+ val newContext = getContextElement(context)
224
+ if (newContext !is KtElement ) return newContext
225
+ return wrapContextIfNeeded(project, context, newContext)
226
+ }
147
227
148
228
override fun createPresentationCodeFragment (item : TextWithImports , context : PsiElement ? , project : Project ): JavaCodeFragment {
149
229
val kotlinCodeFragment = createCodeFragment(item, context, project)
@@ -190,11 +270,16 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
190
270
}
191
271
192
272
override fun isContextAccepted (contextElement : PsiElement ? ): Boolean {
193
- if (contextElement is PsiCodeBlock ) {
194
- // PsiCodeBlock -> DummyHolder -> originalElement
195
- return isContextAccepted(contextElement.context?.context)
273
+ return when {
274
+ // PsiCodeBlock -> DummyHolder -> originalElement
275
+ contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context)
276
+ contextElement == null -> false
277
+ contextElement.language == KotlinFileType .INSTANCE .language -> true
278
+ contextElement.language == JavaFileType .INSTANCE .language -> {
279
+ getKotlinJvmRuntimeMarkerClass(contextElement.project, contextElement.resolveScope) != null
280
+ }
281
+ else -> false
196
282
}
197
- return contextElement?.language == KotlinFileType .INSTANCE .language
198
283
}
199
284
200
285
override fun getFileType () = KotlinFileType .INSTANCE
@@ -206,7 +291,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
206
291
val DEBUG_LABEL_SUFFIX : String = " _DebugLabel"
207
292
@TestOnly val DEBUG_CONTEXT_FOR_TESTS : Key <DebuggerContextImpl > = Key .create(" DEBUG_CONTEXT_FOR_TESTS" )
208
293
209
- fun getContextElement (elementAt : PsiElement ? ): KtElement ? {
294
+ fun getContextElement (elementAt : PsiElement ? ): PsiElement ? {
210
295
if (elementAt == null ) return null
211
296
212
297
if (elementAt is PsiCodeBlock ) {
@@ -218,6 +303,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
218
303
}
219
304
220
305
val containingFile = elementAt.containingFile
306
+ if (containingFile is PsiJavaFile ) return elementAt
221
307
if (containingFile !is KtFile ) return null
222
308
223
309
// elementAt can be PsiWhiteSpace when codeFragment is created from line start offset (in case of first opening EE window)
@@ -300,11 +386,27 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
300
386
return createWrappingContext(text, labels, newContext, project)
301
387
}
302
388
389
+ private fun createFakeFileWithJavaContextElement (funWithLocalVariables : String , javaContext : PsiElement ): KtFile {
390
+ val javaFile = javaContext.containingFile as ? PsiJavaFile
391
+
392
+ val sb = StringBuilder ()
393
+
394
+ javaFile?.packageName?.check { ! it.isBlank() }?.let {
395
+ sb.append(" package " ).append(it.quoteIfNeeded()).append(" \n " )
396
+ }
397
+
398
+ javaFile?.importList?.let { sb.append(it.text).append(" \n " ) }
399
+
400
+ sb.append(funWithLocalVariables)
401
+
402
+ return KtPsiFactory (javaContext.project).createAnalyzableFile(" fakeFileForJavaContextInDebugger.kt" , sb.toString(), javaContext)
403
+ }
404
+
303
405
// internal for test
304
406
fun createWrappingContext (
305
407
newFragmentText : String ,
306
408
labels : Map <String , Value >,
307
- originalContext : PsiElement ? ,
409
+ originalContext : KtElement ? ,
308
410
project : Project
309
411
): KtElement ? {
310
412
val codeFragment = KtPsiFactory (project).createBlockCodeFragment(newFragmentText, originalContext)
@@ -318,6 +420,6 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
318
420
}
319
421
})
320
422
321
- return getContextElement(codeFragment.findElementAt(codeFragment.text.length - 1 ))
423
+ return getContextElement(codeFragment.findElementAt(codeFragment.text.length - 1 )) as ? KtElement
322
424
}
323
425
}
0 commit comments