diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/actions/ShowKotlinBytecodeAction.kt.201 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/actions/ShowKotlinBytecodeAction.kt.201 new file mode 100644 index 00000000000..3993fd63862 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/actions/ShowKotlinBytecodeAction.kt.201 @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.actions + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.wm.ToolWindowAnchor +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.ui.content.ContentFactory +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.KotlinIcons +import org.jetbrains.kotlin.idea.internal.KotlinBytecodeToolWindow + +class ShowKotlinBytecodeAction : AnAction() { + val TOOLWINDOW_ID = "Kotlin Bytecode" + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val toolWindowManager = ToolWindowManager.getInstance(project) + + val toolWindow = toolWindowManager.getToolWindow(TOOLWINDOW_ID) ?: toolWindowManager.registerToolWindow( + "Kotlin Bytecode", + false, + ToolWindowAnchor.RIGHT, + ) + .apply { + setIcon(KotlinIcons.SMALL_LOGO_13) + val contentFactory = ContentFactory.SERVICE.getInstance() + contentManager.addContent(contentFactory.createContent(KotlinBytecodeToolWindow(project, this), "", false)) + } + + toolWindow.activate(null) + } + + override fun update(e: AnActionEvent) { + val file = e.getData(CommonDataKeys.PSI_FILE) + e.presentation.isEnabled = e.project != null && file?.fileType == KotlinFileType.INSTANCE + } +} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/output/ToolWindowScratchOutputHandler.kt.201 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/output/ToolWindowScratchOutputHandler.kt.201 new file mode 100644 index 00000000000..cebce96dc18 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/output/ToolWindowScratchOutputHandler.kt.201 @@ -0,0 +1,274 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.scratch.output + +import com.intellij.execution.filters.OpenFileHyperlinkInfo +import com.intellij.execution.impl.ConsoleViewImpl +import com.intellij.execution.runners.ExecutionUtil +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.ide.scratch.ScratchFileType +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.TransactionGuard +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.ex.EditorEx +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.wm.ToolWindow +import com.intellij.openapi.wm.ToolWindowAnchor +import com.intellij.openapi.wm.ToolWindowFactory +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.idea.scratch.ScratchExpression +import org.jetbrains.kotlin.idea.scratch.ScratchFile +import org.jetbrains.kotlin.psi.KtPsiFactory + +/** + * Method to retrieve shared instance of scratches ToolWindow output handler. + * + * [releaseToolWindowHandler] must be called for every output handler received from this method. + * + * Can be called from EDT only. + * + * @return new toolWindow output handler if one does not exist, otherwise returns the existing one. When application in test mode, + * returns [TestOutputHandler]. + */ +fun requestToolWindowHandler(): ScratchOutputHandler { + return if (ApplicationManager.getApplication().isUnitTestMode) { + TestOutputHandler + } else { + ScratchToolWindowHandlerKeeper.requestOutputHandler() + } +} + +/** + * Should be called once with the output handler received from the [requestToolWindowHandler] call. + * + * When release is called for every request, the output handler is actually disposed. + * + * When application in test mode, does nothing. + * + * Can be called from EDT only. + */ +fun releaseToolWindowHandler(scratchOutputHandler: ScratchOutputHandler) { + if (!ApplicationManager.getApplication().isUnitTestMode) { + ScratchToolWindowHandlerKeeper.releaseOutputHandler(scratchOutputHandler) + } +} + +/** + * Implements logic of shared pointer for the toolWindow output handler. + * + * Not thread safe! Can be used only from the EDT. + */ +private object ScratchToolWindowHandlerKeeper { + private var toolWindowHandler: ScratchOutputHandler? = null + private var toolWindowDisposable = Disposer.newDisposable() + private var counter = 0 + + fun requestOutputHandler(): ScratchOutputHandler { + if (counter == 0) { + toolWindowHandler = ToolWindowScratchOutputHandler(toolWindowDisposable) + } + + counter += 1 + return toolWindowHandler!! + } + + fun releaseOutputHandler(scratchOutputHandler: ScratchOutputHandler) { + require(counter > 0) { "Counter is $counter, nothing to release!" } + require(toolWindowHandler === scratchOutputHandler) { "$scratchOutputHandler differs from stored $toolWindowHandler" } + + counter -= 1 + if (counter == 0) { + Disposer.dispose(toolWindowDisposable) + toolWindowDisposable = Disposer.newDisposable() + toolWindowHandler = null + } + } +} + +private class ToolWindowScratchOutputHandler(private val parentDisposable: Disposable) : ScratchOutputHandlerAdapter() { + + override fun handle(file: ScratchFile, expression: ScratchExpression, output: ScratchOutput) { + printToConsole(file) { + val psiFile = file.getPsiFile() + if (psiFile != null) { + printHyperlink( + getLineInfo(psiFile, expression), + OpenFileHyperlinkInfo( + project, + psiFile.virtualFile, + expression.lineStart + ) + ) + print(" ", ConsoleViewContentType.NORMAL_OUTPUT) + } + print(output.text, output.type.convert()) + } + } + + override fun error(file: ScratchFile, message: String) { + printToConsole(file) { + print(message, ConsoleViewContentType.ERROR_OUTPUT) + } + } + + private fun printToConsole(file: ScratchFile, print: ConsoleViewImpl.() -> Unit) { + ApplicationManager.getApplication().invokeLater { + val project = file.project.takeIf { !it.isDisposed } ?: return@invokeLater + + val toolWindow = getToolWindow(project) ?: createToolWindow(file) + + val contents = toolWindow.contentManager.contents + for (content in contents) { + val component = content.component + if (component is ConsoleViewImpl) { + component.print() + component.print("\n", ConsoleViewContentType.NORMAL_OUTPUT) + } + } + + toolWindow.setAvailable(true, null) + + if (!file.options.isInteractiveMode) { + toolWindow.show(null) + } + + toolWindow.setIcon(ExecutionUtil.getLiveIndicator(ScratchFileType.INSTANCE.icon)) + } + } + + override fun clear(file: ScratchFile) { + ApplicationManager.getApplication().invokeLater { + val toolWindow = getToolWindow(file.project) ?: return@invokeLater + val contents = toolWindow.contentManager.contents + for (content in contents) { + val component = content.component + if (component is ConsoleViewImpl) { + component.clear() + } + } + + if (!file.options.isInteractiveMode) { + toolWindow.hide(null) + } + + toolWindow.setIcon(ScratchFileType.INSTANCE.icon ?: error("Text icon is expected to be present")) + } + } + + private fun ScratchOutputType.convert() = when (this) { + ScratchOutputType.OUTPUT -> ConsoleViewContentType.SYSTEM_OUTPUT + ScratchOutputType.RESULT -> ConsoleViewContentType.NORMAL_OUTPUT + ScratchOutputType.ERROR -> ConsoleViewContentType.ERROR_OUTPUT + } + + private fun getToolWindow(project: Project): ToolWindow? { + val toolWindowManager = ToolWindowManager.getInstance(project) + return toolWindowManager.getToolWindow(ScratchToolWindowFactory.ID) + } + + private fun createToolWindow(file: ScratchFile): ToolWindow { + val project = file.project + val toolWindowManager = ToolWindowManager.getInstance(project) + toolWindowManager.registerToolWindow(ScratchToolWindowFactory.ID, true, ToolWindowAnchor.BOTTOM) + val window = + toolWindowManager.getToolWindow(ScratchToolWindowFactory.ID) ?: error("ScratchToolWindowFactory.ID should be registered") + ScratchToolWindowFactory().createToolWindowContent(project, window) + + Disposer.register(parentDisposable, Disposable { + toolWindowManager.unregisterToolWindow(ScratchToolWindowFactory.ID) + }) + + return window + } +} + +private fun getLineInfo(psiFile: PsiFile, expression: ScratchExpression) = + "${psiFile.name}:${expression.lineStart + 1}" + +private class ScratchToolWindowFactory : ToolWindowFactory { + companion object { + const val ID = "Scratch Output" + } + + override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { + val consoleView = ConsoleViewImpl(project, true) + toolWindow.setToHideOnEmptyContent(true) + toolWindow.setIcon(ScratchFileType.INSTANCE.icon ?: error("Text icon should be present")) + toolWindow.hide(null) + + val contentManager = toolWindow.contentManager + val content = contentManager.factory.createContent(consoleView.component, null, false) + contentManager.addContent(content) + val editor = consoleView.editor + if (editor is EditorEx) { + editor.isRendererMode = true + } + + Disposer.register(project, consoleView) + } +} + +private object TestOutputHandler : ScratchOutputHandlerAdapter() { + private val errors = arrayListOf() + private val inlays = arrayListOf>() + + override fun handle(file: ScratchFile, expression: ScratchExpression, output: ScratchOutput) { + inlays.add(expression to output.text) + } + + override fun error(file: ScratchFile, message: String) { + errors.add(message) + } + + override fun onFinish(file: ScratchFile) { + TransactionGuard.submitTransaction(file.project, Runnable { + val psiFile = file.getPsiFile() + ?: error( + "PsiFile cannot be found for scratch to render inlays in tests:\n" + + "project.isDisposed = ${file.project.isDisposed}\n" + + "inlays = ${inlays.joinToString { it.second }}\n" + + "errors = ${errors.joinToString()}" + ) + + if (inlays.isNotEmpty()) { + testPrint(psiFile, inlays.map { (expression, text) -> + "/** ${getLineInfo(psiFile, expression)} $text */" + }) + inlays.clear() + } + + if (errors.isNotEmpty()) { + testPrint(psiFile, listOf(errors.joinToString(prefix = "/** ", postfix = " */"))) + errors.clear() + } + }) + } + + private fun testPrint(file: PsiFile, comments: List) { + WriteCommandAction.runWriteCommandAction(file.project) { + for (comment in comments) { + file.addAfter( + KtPsiFactory(file.project).createComment(comment), + file.lastChild + ) + } + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinUsageContextDataFlowPanelBase.kt.201 b/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinUsageContextDataFlowPanelBase.kt.201 new file mode 100644 index 00000000000..c824a867eb7 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinUsageContextDataFlowPanelBase.kt.201 @@ -0,0 +1,127 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.slicer + +import com.intellij.analysis.AnalysisScope +import com.intellij.openapi.Disposable +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.wm.ToolWindowId +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.psi.PsiElement +import com.intellij.slicer.DuplicateMap +import com.intellij.slicer.SliceAnalysisParams +import com.intellij.slicer.SlicePanel +import com.intellij.slicer.SliceRootNode +import com.intellij.usageView.UsageInfo +import com.intellij.usageView.UsageViewBundle +import com.intellij.usages.PsiElementUsageTarget +import com.intellij.usages.UsageContextPanel +import com.intellij.usages.UsageView +import com.intellij.usages.UsageViewPresentation +import com.intellij.usages.impl.UsageContextPanelBase +import com.intellij.usages.impl.UsageViewImpl +import org.jetbrains.kotlin.psi.KtDeclaration +import java.awt.BorderLayout +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.SwingConstants + +sealed class KotlinUsageContextDataFlowPanelBase( + project: Project, + presentation: UsageViewPresentation, + private val isInflow: Boolean +) : UsageContextPanelBase(project, presentation) { + private var panel: JPanel? = null + + abstract class ProviderBase : UsageContextPanel.Provider { + override fun isAvailableFor(usageView: UsageView): Boolean { + val target = (usageView as UsageViewImpl).targets.firstOrNull() ?: return false + val element = (target as? PsiElementUsageTarget)?.element + return element is KtDeclaration && element.isValid + } + } + + private fun createParams(element: PsiElement): SliceAnalysisParams { + return SliceAnalysisParams().apply { + scope = AnalysisScope(element.project) + dataFlowToThis = isInflow + showInstanceDereferences = true + } + } + + protected fun createPanel(element: PsiElement, dataFlowToThis: Boolean): JPanel { + val toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.FIND) ?: error("Can't find ToolWindowId.FIND") + val params = createParams(element) + + val rootNode = SliceRootNode(myProject, DuplicateMap(), KotlinSliceUsage(element, params)) + + return object : SlicePanel(myProject, dataFlowToThis, rootNode, false, toolWindow) { + override fun isToShowAutoScrollButton() = false + + override fun isToShowPreviewButton() = false + + override fun isAutoScroll() = false + + override fun setAutoScroll(autoScroll: Boolean) {} + + override fun isPreview() = false + + override fun setPreview(preview: Boolean) {} + } + } + + public override fun updateLayoutLater(infos: List?) { + if (infos == null) { + removeAll() + val title = UsageViewBundle.message("select.the.usage.to.preview", myPresentation.usagesWord) + add(JLabel(title, SwingConstants.CENTER), BorderLayout.CENTER) + } else { + val element = infos.firstOrNull()?.element ?: return + if (panel != null) { + Disposer.dispose(panel as Disposable) + } + + val panel = createPanel(element, isInflow) + Disposer.register(this, panel as Disposable) + removeAll() + add(panel, BorderLayout.CENTER) + this.panel = panel + } + revalidate() + } + + override fun dispose() { + super.dispose() + panel = null + } +} + +class KotlinUsageContextDataInflowPanel( + project: Project, + presentation: UsageViewPresentation +) : KotlinUsageContextDataFlowPanelBase(project, presentation, true) { + class Provider : ProviderBase() { + override fun create(usageView: UsageView): UsageContextPanel { + return KotlinUsageContextDataInflowPanel((usageView as UsageViewImpl).project, usageView.getPresentation()) + } + + override fun getTabTitle() = "Dataflow to Here" + } +} + +class KotlinUsageContextDataOutflowPanel( + project: Project, + presentation: UsageViewPresentation +) : KotlinUsageContextDataFlowPanelBase(project, presentation, false) { + class Provider : ProviderBase() { + override fun create(usageView: UsageView): UsageContextPanel { + return KotlinUsageContextDataOutflowPanel((usageView as UsageViewImpl).project, usageView.getPresentation()) + } + + override fun getTabTitle() = "Dataflow from Here" + } +} \ No newline at end of file