From 976a158ce1816f6f5f8f3a0869e4f4d3ee1ecd7b Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Thu, 21 Dec 2017 19:42:17 +0300 Subject: [PATCH] Support alternative source popup for Kotlin files (KT-21958) #KT-21958 Fixed --- ...inAlternativeSourceNotificationProvider.kt | 170 ++++++++++++++++++ .../idea/debugger/KotlinPositionManager.kt | 17 +- idea/src/META-INF/jvm.xml | 1 + 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinAlternativeSourceNotificationProvider.kt diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinAlternativeSourceNotificationProvider.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinAlternativeSourceNotificationProvider.kt new file mode 100644 index 00000000000..9db313d6183 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinAlternativeSourceNotificationProvider.kt @@ -0,0 +1,170 @@ +/* + * 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.debugger + +import com.intellij.debugger.DebuggerManagerEx +import com.intellij.debugger.engine.JavaStackFrame +import com.intellij.debugger.engine.events.DebuggerCommandImpl +import com.intellij.debugger.impl.DebuggerUtilsEx +import com.intellij.debugger.settings.DebuggerSettings +import com.intellij.ide.util.ModuleRendererFactory +import com.intellij.openapi.fileEditor.FileEditor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiManager +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.ui.EditorNotificationPanel +import com.intellij.ui.EditorNotifications +import com.intellij.ui.components.JBList +import com.intellij.xdebugger.XDebuggerManager +import com.intellij.xdebugger.impl.ui.DebuggerUIUtil +import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil.findFilesWithExactPackage +import org.jetbrains.kotlin.psi.KtFile + +class KotlinAlternativeSourceNotificationProvider(private val myProject: Project) : EditorNotifications.Provider() { + override fun getKey(): Key { + return KEY + } + + override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? { + if (!DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE) { + return null + } + + val session = XDebuggerManager.getInstance(myProject).currentSession + if (session == null) { + FILE_PROCESSED_KEY.set(file, null) + return null + } + + val position = session.currentPosition + if (file != position?.file) { + FILE_PROCESSED_KEY.set(file, null) + return null + } + + if (DumbService.getInstance(myProject).isDumb) return null + + val ktFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile ?: return null + + val packageFqName = ktFile.packageFqName + val fileName = ktFile.name + + val alternativeKtFiles = findFilesWithExactPackage(packageFqName, GlobalSearchScope.allScope(myProject), myProject).filterTo(HashSet()) { + it.name == fileName + } + + FILE_PROCESSED_KEY.set(file, true) + + if (alternativeKtFiles.size <= 1) { + return null + } + + val currentFirstAlternatives: Collection = listOf(ktFile) + alternativeKtFiles.filter { it != ktFile } + + val frame = session.currentStackFrame + val locationDeclName: String? = when (frame) { + is JavaStackFrame -> { + val location = frame.descriptor.location + location?.declaringType()?.name() + } + else -> null + } + + return AlternativeSourceNotificationPanel(currentFirstAlternatives, myProject, file, locationDeclName) + } + + private class AlternativeSourceNotificationPanel( + alternatives: Collection, + project: Project, + file: VirtualFile, + locationDeclName: String? + ) : EditorNotificationPanel() { + private class ComboBoxFileElement(val ktFile: KtFile) { + private val label: String by lazy(LazyThreadSafetyMode.NONE) { + val factory = ModuleRendererFactory.findInstance(ktFile) + val moduleRenderer = factory.moduleRenderer + moduleRenderer.getListCellRendererComponent(ourDummyList, ktFile, 1, false, false) + moduleRenderer.text ?: "" + } + + override fun toString(): String = label + + companion object { + private val ourDummyList = JBList() + } + } + + init { + setText("Alternative source available for file ${file.name}") + + val items = alternatives.map { ComboBoxFileElement(it) } + myLinksPanel.add( + ComboBox(items.toTypedArray()).apply { + addActionListener { + val context = DebuggerManagerEx.getInstanceEx(project).context + val session = context.debuggerSession + val ktFile = (selectedItem as ComboBoxFileElement).ktFile + val vFile = ktFile.containingFile.virtualFile + + when { + session != null && vFile != null -> + session.process.managerThread.schedule(object : DebuggerCommandImpl() { + override fun action() { + if (!StringUtil.isEmpty(locationDeclName)) { + DebuggerUtilsEx.setAlternativeSourceUrl(locationDeclName, vFile.url, project) + } + + DebuggerUIUtil.invokeLater { + FileEditorManager.getInstance(project).closeFile(file) + session.refresh(true) + } + } + }) + else -> { + FileEditorManager.getInstance(project).closeFile(file) + ktFile.navigate(true) + } + } + } + }) + + createActionLabel("Disable") { + DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE = false + FILE_PROCESSED_KEY.set(file, null) + val fileEditorManager = FileEditorManager.getInstance(project) + val editor = fileEditorManager.getSelectedEditor(file) + if (editor != null) { + fileEditorManager.removeTopComponent(editor, this) + } + } + } + } + + companion object { + private val KEY = Key.create("KotlinAlternativeSource") + + // FIXME: Share AlternativeSourceNotificationProvider.FILE_PROCESSED_KEY + @Suppress("UNCHECKED_CAST", "DEPRECATION") + private val FILE_PROCESSED_KEY = Key.findKeyByName("AlternativeSourceCheckDone") as Key + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index 068d83a5fed..1310c6de63f 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -26,6 +26,7 @@ import com.intellij.debugger.engine.evaluation.EvaluationContext import com.intellij.debugger.impl.DebuggerUtilsEx import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.requests.ClassPrepareRequestor +import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiFile import com.intellij.psi.impl.compiled.ClsFileImpl import com.intellij.psi.search.GlobalSearchScope @@ -87,7 +88,10 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq if (!DebuggerUtils.isKotlinSourceFile(fileName)) throw NoDataException.INSTANCE - val psiFile = getPsiFileByLocation(location) + val psiFile = getPsiFileByLocation(location)?.let { + replaceWithAlternativeSource(it, location) + } + if (psiFile == null) { val isKotlinStrataAvailable = location.declaringType().containsKotlinStrata() if (isKotlinStrataAvailable) { @@ -148,6 +152,17 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq class KotlinReentrantSourcePosition(delegate: SourcePosition) : DelegateSourcePosition(delegate) + private fun replaceWithAlternativeSource(psiFile: PsiFile, location: Location): PsiFile { + fun findAlternativeSource(): PsiFile? { + val qName = location.declaringType().name() + val alternativeFileUrl = DebuggerUtilsEx.getAlternativeSourceUrl(qName, myDebugProcess.project) ?: return null + val alternativePsiFile = VirtualFileManager.getInstance().findFileByUrl(alternativeFileUrl) ?: return null + return psiFile.manager.findFile(alternativePsiFile) + } + + return findAlternativeSource() ?: psiFile + } + // Returns a property or a constructor if debugger stops at class declaration private fun getElementForDeclarationLine(location: Location, file: KtFile, lineNumber: Int): KtElement? { val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null diff --git a/idea/src/META-INF/jvm.xml b/idea/src/META-INF/jvm.xml index 8b41620a2f8..fc7b997bcbe 100644 --- a/idea/src/META-INF/jvm.xml +++ b/idea/src/META-INF/jvm.xml @@ -94,6 +94,7 @@ +