Support alternative source popup for Kotlin files (KT-21958)
#KT-21958 Fixed
This commit is contained in:
+170
@@ -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<EditorNotificationPanel>() {
|
||||||
|
override fun getKey(): Key<EditorNotificationPanel> {
|
||||||
|
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<KtFile> = 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<KtFile>,
|
||||||
|
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<KtFile>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
setText("Alternative source available for file ${file.name}")
|
||||||
|
|
||||||
|
val items = alternatives.map { ComboBoxFileElement(it) }
|
||||||
|
myLinksPanel.add(
|
||||||
|
ComboBox<ComboBoxFileElement>(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<EditorNotificationPanel>("KotlinAlternativeSource")
|
||||||
|
|
||||||
|
// FIXME: Share AlternativeSourceNotificationProvider.FILE_PROCESSED_KEY
|
||||||
|
@Suppress("UNCHECKED_CAST", "DEPRECATION")
|
||||||
|
private val FILE_PROCESSED_KEY = Key.findKeyByName("AlternativeSourceCheckDone") as Key<Boolean>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import com.intellij.debugger.engine.evaluation.EvaluationContext
|
|||||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.requests.ClassPrepareRequestor
|
import com.intellij.debugger.requests.ClassPrepareRequestor
|
||||||
|
import com.intellij.openapi.vfs.VirtualFileManager
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
@@ -87,7 +88,10 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
|
|
||||||
if (!DebuggerUtils.isKotlinSourceFile(fileName)) throw NoDataException.INSTANCE
|
if (!DebuggerUtils.isKotlinSourceFile(fileName)) throw NoDataException.INSTANCE
|
||||||
|
|
||||||
val psiFile = getPsiFileByLocation(location)
|
val psiFile = getPsiFileByLocation(location)?.let {
|
||||||
|
replaceWithAlternativeSource(it, location)
|
||||||
|
}
|
||||||
|
|
||||||
if (psiFile == null) {
|
if (psiFile == null) {
|
||||||
val isKotlinStrataAvailable = location.declaringType().containsKotlinStrata()
|
val isKotlinStrataAvailable = location.declaringType().containsKotlinStrata()
|
||||||
if (isKotlinStrataAvailable) {
|
if (isKotlinStrataAvailable) {
|
||||||
@@ -148,6 +152,17 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
|
|
||||||
class KotlinReentrantSourcePosition(delegate: SourcePosition) : DelegateSourcePosition(delegate)
|
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
|
// Returns a property or a constructor if debugger stops at class declaration
|
||||||
private fun getElementForDeclarationLine(location: Location, file: KtFile, lineNumber: Int): KtElement? {
|
private fun getElementForDeclarationLine(location: Location, file: KtFile, lineNumber: Int): KtElement? {
|
||||||
val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
|
val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
|
||||||
|
|||||||
@@ -94,6 +94,7 @@
|
|||||||
<library.javaSourceRootDetector implementation="org.jetbrains.kotlin.idea.configuration.KotlinSourceRootDetector"/>
|
<library.javaSourceRootDetector implementation="org.jetbrains.kotlin.idea.configuration.KotlinSourceRootDetector"/>
|
||||||
|
|
||||||
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.configuration.KotlinSetupEnvironmentNotificationProvider"/>
|
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.configuration.KotlinSetupEnvironmentNotificationProvider"/>
|
||||||
|
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.debugger.KotlinAlternativeSourceNotificationProvider"/>
|
||||||
|
|
||||||
<consoleFilterProvider implementation="org.jetbrains.kotlin.idea.run.KotlinConsoleFilterProvider"/>
|
<consoleFilterProvider implementation="org.jetbrains.kotlin.idea.run.KotlinConsoleFilterProvider"/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user