Debugger: add imports from file on breakpoint
This commit is contained in:
@@ -23,14 +23,29 @@ import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.Pair
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import org.jetbrains.jet.plugin.JetFileType
|
||||
import org.jetbrains.jet.lang.psi.JetFile
|
||||
import org.jetbrains.jet.lang.psi.JetExpressionCodeFragmentImpl
|
||||
|
||||
class KotlinEditorTextProvider : EditorTextProvider {
|
||||
override fun getEditorText(elementAtCaret: PsiElement): TextWithImports? {
|
||||
return TextWithImportsImpl(CodeFragmentKind.EXPRESSION, elementAtCaret.getText())
|
||||
return TextWithImportsImpl(CodeFragmentKind.EXPRESSION, elementAtCaret.getText(), getImports(elementAtCaret), JetFileType.INSTANCE)
|
||||
}
|
||||
|
||||
override fun findExpression(elementAtCaret: PsiElement, allowMethodCalls: Boolean): Pair<PsiElement, TextRange>? {
|
||||
return Pair(elementAtCaret, elementAtCaret.getTextRange())
|
||||
}
|
||||
|
||||
class object {
|
||||
fun getImports(elementAtCaret: PsiElement): String {
|
||||
val containingFile = elementAtCaret.getContainingFile()
|
||||
if (containingFile !is JetFile) return ""
|
||||
|
||||
return containingFile.getImportList()?.getImports()
|
||||
?.map { it.getText() }
|
||||
?.makeString(JetExpressionCodeFragmentImpl.IMPORT_SEPARATOR) ?: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,11 @@ import org.jetbrains.jet.lang.psi.JetExpressionCodeFragmentImpl
|
||||
|
||||
class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
||||
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||
return JetExpressionCodeFragmentImpl(project, "fragment.kt", item.getText(), context)
|
||||
val codeFragment = JetExpressionCodeFragmentImpl(project, "fragment.kt", item.getText(), context)
|
||||
if (item.getImports().isNotEmpty()) {
|
||||
codeFragment.addImportsFromString(item.getImports())
|
||||
}
|
||||
return codeFragment
|
||||
}
|
||||
|
||||
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||
|
||||
@@ -50,10 +50,27 @@ import org.jetbrains.jet.lang.resolve.java.PackageClassUtils
|
||||
import org.jetbrains.jet.lang.resolve.name.FqName
|
||||
import org.jetbrains.jet.plugin.caches.resolve.KotlinDeclarationsCache
|
||||
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
|
||||
import java.io.File
|
||||
import org.jetbrains.jet.plugin.debugger.KotlinEditorTextProvider
|
||||
import org.jetbrains.jet.OutputFileCollection
|
||||
import org.jetbrains.jet.lang.psi.JetExpressionCodeFragment
|
||||
import org.jetbrains.jet.lang.psi.JetExpressionCodeFragmentImpl
|
||||
|
||||
object KotlinEvaluationBuilder: EvaluatorBuilder {
|
||||
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
|
||||
if (codeFragment !is JetExpressionCodeFragment) {
|
||||
return EvaluatorBuilderImpl.getInstance()!!.build(codeFragment, position)
|
||||
}
|
||||
|
||||
val elementAt = position?.getElementAt()
|
||||
if (elementAt != null) {
|
||||
codeFragment.addImportsFromString(KotlinEditorTextProvider.getImports(elementAt))
|
||||
|
||||
val packageName = (elementAt.getContainingFile() as JetFile).getPackageDirective()?.getFqName()?.asString()
|
||||
if (packageName != null) {
|
||||
codeFragment.addImportsFromString("import $packageName.*")
|
||||
}
|
||||
}
|
||||
return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment))
|
||||
}
|
||||
}
|
||||
@@ -62,10 +79,11 @@ class KotlinEvaluator(val codeFragment: PsiElement) : Evaluator {
|
||||
override fun evaluate(context: EvaluationContextImpl): Any? {
|
||||
return ApplicationManager.getApplication()?.runReadAction(object: Computable<Any> {
|
||||
override fun compute(): Any? {
|
||||
val fileText = template.replace("!EXPRESSION!", codeFragment.getText())
|
||||
val virtualFile = LightVirtualFile("debugFile.kt", JetLanguage.INSTANCE, fileText)
|
||||
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET)
|
||||
val file = (PsiFileFactory.getInstance(codeFragment.getProject()) as PsiFileFactoryImpl).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false) as JetFile
|
||||
if (codeFragment !is JetExpressionCodeFragment) {
|
||||
throw AssertionError("KotlinEvaluator should be invoke only for KotlinCodeFragment")
|
||||
}
|
||||
|
||||
val file = createFileForDebugger(codeFragment)
|
||||
|
||||
val analyzeExhaust = AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationAndCheckForErrors(
|
||||
file.getProject(),
|
||||
@@ -80,7 +98,7 @@ class KotlinEvaluator(val codeFragment: PsiElement) : Evaluator {
|
||||
AnalyzingUtils.throwExceptionOnErrors(bindingContext)
|
||||
}
|
||||
catch (e: IllegalStateException) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(e.getMessage())
|
||||
exception(e.getMessage() ?: "Exception from kotlin compiler")
|
||||
}
|
||||
|
||||
val state = GenerationState(
|
||||
@@ -146,9 +164,24 @@ class KotlinEvaluator(val codeFragment: PsiElement) : Evaluator {
|
||||
private val template = """
|
||||
package packageForDebugger
|
||||
|
||||
!IMPORT_LIST!
|
||||
|
||||
fun debugFun() = run {
|
||||
!EXPRESSION!
|
||||
}
|
||||
"""
|
||||
|
||||
private val packageInternalName = PackageClassUtils.getPackageClassFqName(FqName("packageForDebugger")).asString().replace(".", "/")
|
||||
private val packageInternalName = PackageClassUtils.getPackageClassFqName(FqName("packageForDebugger")).asString().replace(".", "/")
|
||||
|
||||
private fun createFileForDebugger(codeFragment: JetExpressionCodeFragment): JetFile {
|
||||
var fileText = template.replace("!EXPRESSION!", codeFragment.getText())
|
||||
fileText = fileText.replace("!IMPORT_LIST!",
|
||||
codeFragment.importsToString()
|
||||
.split(JetExpressionCodeFragmentImpl.IMPORT_SEPARATOR)
|
||||
.makeString("\n"))
|
||||
|
||||
val virtualFile = LightVirtualFile("debugFile.kt", JetLanguage.INSTANCE, fileText)
|
||||
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET)
|
||||
return (PsiFileFactory.getInstance(codeFragment.getProject()) as PsiFileFactoryImpl)
|
||||
.trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false) as JetFile
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
LineBreakpoint created at imports.kt:10
|
||||
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!RT_JAR! imports.ImportsPackage
|
||||
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
imports.kt:9
|
||||
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -0,0 +1,23 @@
|
||||
package imports
|
||||
|
||||
import java.util.Collections
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet
|
||||
import java.util.HashMap as JHashMap
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
//Breakpoint!
|
||||
args.size
|
||||
}
|
||||
|
||||
// EXPRESSION: Collections.emptyList<String>()
|
||||
// RESULT: instance of java.util.Collections$EmptyList(id=337): Ljava/util/Collections$EmptyList;
|
||||
|
||||
// EXPRESSION: ArrayList<Int>()
|
||||
// RESULT: instance of java.util.ArrayList(id=383): Ljava/util/ArrayList;
|
||||
|
||||
// EXPRESSION: HashSet<Int>()
|
||||
// RESULT: instance of java.util.HashSet(id=387): Ljava/util/HashSet;
|
||||
|
||||
// EXPRESSION: JHashMap<Int, Int>()
|
||||
// RESULT: instance of java.util.HashMap(id=391): Ljava/util/HashMap;
|
||||
+3
-3
@@ -77,12 +77,12 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestC
|
||||
private fun SuspendContextImpl.evaluate(expression: String, expectedResult: String) {
|
||||
try {
|
||||
val sourcePosition = ContextUtil.getSourcePosition(this)
|
||||
val contextElement = sourcePosition?.getElementAt()
|
||||
Assert.assertTrue("KotlinCodeFragmentFactory should be accepted for context element otherwise default evaluator will be called. ContextElement = ${contextElement?.getText()}",
|
||||
val contextElement = sourcePosition?.getElementAt()!!
|
||||
Assert.assertTrue("KotlinCodeFragmentFactory should be accepted for context element otherwise default evaluator will be called. ContextElement = ${contextElement.getText()}",
|
||||
KotlinCodeFragmentFactory().isContextAccepted(contextElement))
|
||||
|
||||
val evaluator = DebuggerInvocationUtil.commitAndRunReadAction(getProject()) {
|
||||
EvaluatorBuilderImpl.build(TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expression, "", JetFileType.INSTANCE),
|
||||
EvaluatorBuilderImpl.build(TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expression, KotlinEditorTextProvider.getImports(contextElement), JetFileType.INSTANCE),
|
||||
contextElement,
|
||||
sourcePosition)
|
||||
}
|
||||
|
||||
+5
@@ -51,6 +51,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/collections.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("imports.kt")
|
||||
public void testImports() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/imports.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/simple.kt");
|
||||
|
||||
Reference in New Issue
Block a user